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,778 @@
1
+ use crate::encoding::{
2
+ self, Error as EncodingError, SiaDecodable, SiaDecode, SiaEncodable, SiaEncode,
3
+ };
4
+ use crate::encoding_async::{
5
+ AsyncDecoder, AsyncEncoder, AsyncSiaDecodable, AsyncSiaDecode, AsyncSiaEncodable,
6
+ AsyncSiaEncode,
7
+ };
8
+ use crate::signing::{PublicKey, Signature};
9
+ #[allow(deprecated)]
10
+ use crate::types::v1::UnlockConditions;
11
+ use crate::types::{Address, Hash256};
12
+ use blake2b_simd::Params;
13
+ use chrono::{DateTime, Utc};
14
+ use core::fmt;
15
+ use serde::de::{self, MapAccess, Visitor};
16
+ use serde::ser::SerializeStruct;
17
+ use serde::{Deserialize, Serialize};
18
+ use serde_json::json;
19
+ use thiserror::Error;
20
+
21
+ const POLICY_ABOVE_PREFIX: u8 = 1;
22
+ const POLICY_AFTER_PREFIX: u8 = 2;
23
+ const POLICY_PUBLIC_KEY_PREFIX: u8 = 3;
24
+ const POLICY_HASH_PREFIX: u8 = 4;
25
+ const POLICY_THRESHOLD_PREFIX: u8 = 5;
26
+ const POLICY_OPAQUE_PREFIX: u8 = 6;
27
+ #[deprecated]
28
+ const POLICY_UNLOCK_CONDITIONS_PREFIX: u8 = 7;
29
+
30
+ const POLICY_ABOVE_STR: &str = "above";
31
+ const POLICY_AFTER_STR: &str = "after";
32
+ const POLICY_PUBLIC_KEY_STR: &str = "pk";
33
+ const POLICY_HASH_STR: &str = "h";
34
+ const POLICY_THRESHOLD_STR: &str = "thresh";
35
+ const POLICY_OPAQUE_STR: &str = "opaque";
36
+ #[deprecated]
37
+ const POLICY_UNLOCK_CONDITIONS_STR: &str = "uc";
38
+
39
+ #[derive(Debug, PartialEq, Error)]
40
+ pub enum ValidationError {
41
+ #[error("opaque policy")]
42
+ OpaquePolicy,
43
+ #[error("invalid policy")]
44
+ InvalidPolicy,
45
+ #[error("invalid signature")]
46
+ InvalidSignature,
47
+ #[error("invalid preimage")]
48
+ InvalidPreimage,
49
+ #[error("invalid height")]
50
+ InvalidHeight,
51
+ #[error("invalid timestamp")]
52
+ InvalidTimestamp,
53
+ #[error("missing signature")]
54
+ MissingSignature,
55
+ #[error("missing preimage")]
56
+ MissingPreimage,
57
+ #[error("threshold not met")]
58
+ ThresholdNotMet,
59
+ }
60
+
61
+ /// A spend policy is a condition or set of conditions that must be met in
62
+ /// order to spend a UTXO.
63
+ #[derive(Debug, PartialEq, Clone)]
64
+ pub enum SpendPolicy {
65
+ /// A policy that is only valid after a block height
66
+ Above(u64),
67
+ /// A policy that is only valid after a timestamp
68
+ After(DateTime<Utc>),
69
+ /// A policy that requires a valid signature from an ed25519 key pair
70
+ PublicKey(PublicKey),
71
+ /// A policy that requires a valid SHA256 hash preimage
72
+ Hash(Hash256),
73
+ /// A threshold policy that requires n-of-m sub-policies to be met
74
+ Threshold(u8, Vec<SpendPolicy>),
75
+ /// An opaque policy that is not directly spendable
76
+ Opaque(Address),
77
+
78
+ /// A set of v1 unlock conditions for compatibility with v1 transactions
79
+ #[deprecated]
80
+ UnlockConditions(UnlockConditions),
81
+ }
82
+
83
+ impl SpendPolicy {
84
+ fn type_prefix(&self) -> u8 {
85
+ match self {
86
+ SpendPolicy::Above(_) => POLICY_ABOVE_PREFIX,
87
+ SpendPolicy::After(_) => POLICY_AFTER_PREFIX,
88
+ SpendPolicy::PublicKey(_) => POLICY_PUBLIC_KEY_PREFIX,
89
+ SpendPolicy::Hash(_) => POLICY_HASH_PREFIX,
90
+ SpendPolicy::Threshold(_, _) => POLICY_THRESHOLD_PREFIX,
91
+ SpendPolicy::Opaque(_) => POLICY_OPAQUE_PREFIX,
92
+ #[allow(deprecated)]
93
+ SpendPolicy::UnlockConditions(_) => POLICY_UNLOCK_CONDITIONS_PREFIX,
94
+ }
95
+ }
96
+
97
+ fn type_str(&self) -> &str {
98
+ match self {
99
+ SpendPolicy::Above(_) => POLICY_ABOVE_STR,
100
+ SpendPolicy::After(_) => POLICY_AFTER_STR,
101
+ SpendPolicy::PublicKey(_) => POLICY_PUBLIC_KEY_STR,
102
+ SpendPolicy::Hash(_) => POLICY_HASH_STR,
103
+ SpendPolicy::Threshold(_, _) => POLICY_THRESHOLD_STR,
104
+ SpendPolicy::Opaque(_) => POLICY_OPAQUE_STR,
105
+ #[allow(deprecated)]
106
+ SpendPolicy::UnlockConditions(_) => POLICY_UNLOCK_CONDITIONS_STR,
107
+ }
108
+ }
109
+
110
+ /// Create a policy that is only valid after a certain block height
111
+ pub fn above(height: u64) -> Self {
112
+ Self::Above(height)
113
+ }
114
+
115
+ /// Create a policy that is only valid after a certain timestamp
116
+ pub fn after(timestamp: DateTime<Utc>) -> Self {
117
+ Self::After(timestamp)
118
+ }
119
+
120
+ /// Create a policy that requires a valid signature from a public key
121
+ pub fn public_key(pk: PublicKey) -> Self {
122
+ Self::PublicKey(pk)
123
+ }
124
+
125
+ /// Create a policy that requires a hash preimage
126
+ pub fn hash(hash: Hash256) -> Self {
127
+ Self::Hash(hash)
128
+ }
129
+
130
+ /// Create a threshold policy with n-of-m sub-policies
131
+ pub fn threshold(n: u8, policies: Vec<SpendPolicy>) -> Self {
132
+ for policy in policies.iter() {
133
+ #[allow(deprecated)]
134
+ if let SpendPolicy::UnlockConditions(_) = policy {
135
+ panic!("UnlockConditions are not allowed in a threshold policy");
136
+ }
137
+ }
138
+ Self::Threshold(n, policies)
139
+ }
140
+
141
+ /// Create a v1 unlock conditions policy for compatibility with v1
142
+ /// transactions.
143
+ #[deprecated]
144
+ pub fn unlock_conditions(uc: UnlockConditions) -> Self {
145
+ #[allow(deprecated)]
146
+ Self::UnlockConditions(uc)
147
+ }
148
+
149
+ /// Returns the address of the policy. This is a hash of the policy that
150
+ /// can be used to receive funds.
151
+ pub fn address(&self) -> Address {
152
+ #[allow(deprecated)]
153
+ if let SpendPolicy::UnlockConditions(uc) = self {
154
+ return uc.address();
155
+ } else if let SpendPolicy::Opaque(addr) = self {
156
+ return addr.clone();
157
+ }
158
+
159
+ let mut state = Params::new().hash_length(32).to_state();
160
+ state.update("sia/address|".as_bytes());
161
+
162
+ if let SpendPolicy::Threshold(n, of) = self {
163
+ let mut opaque = Vec::with_capacity(of.len());
164
+ for policy in of {
165
+ opaque.push(SpendPolicy::Opaque(policy.address()))
166
+ }
167
+ SpendPolicy::Threshold(*n, opaque)
168
+ .encode(&mut state)
169
+ .unwrap();
170
+ } else {
171
+ self.encode(&mut state).unwrap();
172
+ }
173
+ Address::from(state.finalize().as_bytes())
174
+ }
175
+ }
176
+
177
+ impl<'de> Deserialize<'de> for SpendPolicy {
178
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
179
+ where
180
+ D: serde::Deserializer<'de>,
181
+ {
182
+ struct SpendPolicyVisitor;
183
+
184
+ impl<'de> Visitor<'de> for SpendPolicyVisitor {
185
+ type Value = SpendPolicy;
186
+
187
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
188
+ formatter.write_str("a spend policy")
189
+ }
190
+
191
+ // json encoding
192
+ fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
193
+ where
194
+ V: MapAccess<'de>,
195
+ {
196
+ let mut policy_type: Option<String> = None;
197
+ let mut policy_value: Option<serde_json::Value> = None;
198
+
199
+ while let Some(key) = map.next_key::<String>()? {
200
+ match key.as_str() {
201
+ "type" => {
202
+ policy_type = Some(map.next_value()?);
203
+ }
204
+ "policy" => {
205
+ policy_value = Some(map.next_value()?);
206
+ }
207
+ _ => return Err(de::Error::unknown_field(&key, &["type", "policy"])),
208
+ }
209
+ }
210
+
211
+ let policy_type = policy_type.ok_or_else(|| de::Error::missing_field("type"))?;
212
+ let policy_value =
213
+ policy_value.ok_or_else(|| de::Error::missing_field("policy"))?;
214
+
215
+ match policy_type.as_str() {
216
+ POLICY_ABOVE_STR => {
217
+ let height =
218
+ serde_json::from_value(policy_value).map_err(de::Error::custom)?;
219
+ Ok(SpendPolicy::Above(height))
220
+ }
221
+ POLICY_AFTER_STR => {
222
+ let unix_seconds: u64 =
223
+ serde_json::from_value(policy_value).map_err(de::Error::custom)?;
224
+ let timestamp = DateTime::from_timestamp_secs(unix_seconds as i64)
225
+ .ok_or(de::Error::custom("invalid timestamp"))?;
226
+ Ok(SpendPolicy::After(timestamp))
227
+ }
228
+ POLICY_PUBLIC_KEY_STR => {
229
+ let pk: PublicKey =
230
+ serde_json::from_value(policy_value).map_err(de::Error::custom)?;
231
+ Ok(SpendPolicy::PublicKey(pk))
232
+ }
233
+ POLICY_HASH_STR => {
234
+ let hash: Hash256 =
235
+ serde_json::from_value(policy_value).map_err(de::Error::custom)?;
236
+ Ok(SpendPolicy::Hash(hash))
237
+ }
238
+ POLICY_THRESHOLD_STR => {
239
+ #[derive(Deserialize)]
240
+ struct ThreshPolicy {
241
+ n: u8,
242
+ of: Vec<SpendPolicy>,
243
+ }
244
+ let thresh: ThreshPolicy =
245
+ serde_json::from_value(policy_value).map_err(de::Error::custom)?;
246
+ Ok(SpendPolicy::Threshold(thresh.n, thresh.of))
247
+ }
248
+ POLICY_OPAQUE_STR => {
249
+ let addr: Address =
250
+ serde_json::from_value(policy_value).map_err(de::Error::custom)?;
251
+ Ok(SpendPolicy::Opaque(addr))
252
+ }
253
+ #[allow(deprecated)]
254
+ POLICY_UNLOCK_CONDITIONS_STR => {
255
+ #[allow(deprecated)]
256
+ let uc: UnlockConditions =
257
+ serde_json::from_value(policy_value).map_err(de::Error::custom)?;
258
+ #[allow(deprecated)]
259
+ Ok(SpendPolicy::UnlockConditions(uc))
260
+ }
261
+ _ => Err(de::Error::unknown_variant(
262
+ &policy_type,
263
+ #[allow(deprecated)]
264
+ &[
265
+ POLICY_ABOVE_STR,
266
+ POLICY_AFTER_STR,
267
+ POLICY_PUBLIC_KEY_STR,
268
+ POLICY_HASH_STR,
269
+ POLICY_THRESHOLD_STR,
270
+ POLICY_OPAQUE_STR,
271
+ POLICY_UNLOCK_CONDITIONS_STR,
272
+ ],
273
+ )),
274
+ }
275
+ }
276
+ }
277
+
278
+ deserializer.deserialize_map(SpendPolicyVisitor)
279
+ }
280
+ }
281
+
282
+ impl Serialize for SpendPolicy {
283
+ fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
284
+ let mut state = serializer.serialize_struct("SpendPolicy", 2)?;
285
+ state.serialize_field("type", self.type_str())?;
286
+ match self {
287
+ SpendPolicy::Above(height) => {
288
+ state.serialize_field("policy", height)?;
289
+ }
290
+ SpendPolicy::After(time) => {
291
+ let unix_seconds = time.timestamp() as u64;
292
+ state.serialize_field("policy", &unix_seconds)?;
293
+ }
294
+ SpendPolicy::PublicKey(pk) => {
295
+ state.serialize_field("policy", &pk)?;
296
+ }
297
+ SpendPolicy::Hash(hash) => {
298
+ state.serialize_field("policy", &hash)?;
299
+ }
300
+ SpendPolicy::Threshold(n, policies) => {
301
+ state.serialize_field(
302
+ "policy",
303
+ &json!({
304
+ "n": n,
305
+ "of": policies,
306
+ }),
307
+ )?;
308
+ }
309
+ SpendPolicy::Opaque(addr) => {
310
+ state.serialize_field("policy", addr)?;
311
+ }
312
+ #[allow(deprecated)]
313
+ SpendPolicy::UnlockConditions(uc) => {
314
+ state.serialize_field("policy", uc)?;
315
+ }
316
+ }
317
+ state.end()
318
+ }
319
+ }
320
+
321
+ impl SiaEncodable for SpendPolicy {
322
+ fn encode<W: std::io::Write>(&self, w: &mut W) -> encoding::Result<()> {
323
+ // helper to recursively encode policies
324
+ fn encode_policy<W: std::io::Write>(
325
+ policy: &SpendPolicy,
326
+ w: &mut W,
327
+ ) -> encoding::Result<()> {
328
+ w.write_all(&[policy.type_prefix()])?;
329
+ match policy {
330
+ SpendPolicy::Above(height) => height.encode(w),
331
+ SpendPolicy::After(time) => (time.timestamp() as u64).encode(w),
332
+ SpendPolicy::PublicKey(pk) => pk.encode(w),
333
+ SpendPolicy::Hash(hash) => hash.encode(w),
334
+ SpendPolicy::Threshold(of, policies) => {
335
+ of.encode(w)?;
336
+ (policies.len() as u8).encode(w)?;
337
+ for policy in policies {
338
+ encode_policy(policy, w)?;
339
+ }
340
+ Ok(())
341
+ }
342
+ SpendPolicy::Opaque(addr) => addr.encode(w),
343
+ #[allow(deprecated)]
344
+ SpendPolicy::UnlockConditions(uc) => uc.encode(w),
345
+ }
346
+ }
347
+ 1u8.encode(w)?;
348
+ encode_policy(self, w)
349
+ }
350
+ }
351
+
352
+ impl SiaDecodable for SpendPolicy {
353
+ fn decode<R: std::io::Read>(r: &mut R) -> encoding::Result<Self> {
354
+ // helper to recursively decode policies
355
+ fn decode_policy<R: std::io::Read>(r: &mut R) -> encoding::Result<SpendPolicy> {
356
+ let policy_type = u8::decode(r)?;
357
+ match policy_type {
358
+ POLICY_ABOVE_PREFIX => Ok(SpendPolicy::Above(u64::decode(r)?)),
359
+ POLICY_AFTER_PREFIX => {
360
+ let unix_seconds = u64::decode(r)?;
361
+ let timestamp = DateTime::from_timestamp_secs(unix_seconds as i64)
362
+ .ok_or(EncodingError::Custom("invalid timestamp".to_string()))?;
363
+ Ok(SpendPolicy::After(timestamp))
364
+ }
365
+ POLICY_PUBLIC_KEY_PREFIX => Ok(SpendPolicy::PublicKey(PublicKey::decode(r)?)),
366
+ POLICY_HASH_PREFIX => Ok(SpendPolicy::Hash(Hash256::decode(r)?)),
367
+ POLICY_THRESHOLD_PREFIX => {
368
+ let of: u8 = u8::decode(r)?;
369
+ let n = u8::decode(r)?;
370
+ let mut policies = Vec::with_capacity(n as usize);
371
+ while policies.len() < n as usize {
372
+ policies.push(decode_policy(r)?);
373
+ }
374
+ Ok(SpendPolicy::Threshold(of, policies))
375
+ }
376
+ POLICY_OPAQUE_PREFIX => Ok(SpendPolicy::Opaque(Address::decode(r)?)),
377
+ #[allow(deprecated)]
378
+ POLICY_UNLOCK_CONDITIONS_PREFIX => {
379
+ Ok(SpendPolicy::UnlockConditions(UnlockConditions::decode(r)?))
380
+ }
381
+ _ => Err(encoding::Error::Custom("invalid policy type".to_string())),
382
+ }
383
+ }
384
+ let policy_version = u8::decode(r)?;
385
+ if policy_version != 1 {
386
+ return Err(encoding::Error::Custom(
387
+ "invalid policy version".to_string(),
388
+ ));
389
+ }
390
+ decode_policy(r)
391
+ }
392
+ }
393
+
394
+ impl AsyncSiaEncodable for SpendPolicy {
395
+ async fn encode_async<E: AsyncEncoder>(&self, e: &mut E) -> Result<(), E::Error> {
396
+ // helper to recursively encode policies
397
+ async fn encode_policy<E: AsyncEncoder>(
398
+ policy: &SpendPolicy,
399
+ e: &mut E,
400
+ ) -> Result<(), E::Error> {
401
+ e.encode_buf(&[policy.type_prefix()]).await?;
402
+ match policy {
403
+ SpendPolicy::Above(height) => height.encode_async(e).await,
404
+ SpendPolicy::After(time) => (time.timestamp() as u64).encode_async(e).await,
405
+ SpendPolicy::PublicKey(pk) => pk.encode_async(e).await,
406
+ SpendPolicy::Hash(hash) => hash.encode_async(e).await,
407
+ SpendPolicy::Threshold(of, policies) => {
408
+ of.encode_async(e).await?;
409
+ (policies.len() as u8).encode_async(e).await?;
410
+ for policy in policies {
411
+ encode_policy(policy, e).await?;
412
+ }
413
+ Ok(())
414
+ }
415
+ SpendPolicy::Opaque(addr) => addr.encode_async(e).await,
416
+ #[allow(deprecated)]
417
+ SpendPolicy::UnlockConditions(uc) => uc.encode_async(e).await,
418
+ }
419
+ }
420
+ 1u8.encode_async(e).await?;
421
+ encode_policy(self, e).await
422
+ }
423
+ }
424
+
425
+ impl AsyncSiaDecodable for SpendPolicy {
426
+ async fn decode_async<D: AsyncDecoder>(d: &mut D) -> Result<Self, D::Error> {
427
+ // helper to recursively decode policies
428
+ async fn decode_policy<D: AsyncDecoder>(d: &mut D) -> Result<SpendPolicy, D::Error> {
429
+ let policy_type = u8::decode_async(d).await?;
430
+ match policy_type {
431
+ POLICY_ABOVE_PREFIX => Ok(SpendPolicy::Above(u64::decode_async(d).await?)),
432
+ POLICY_AFTER_PREFIX => {
433
+ Ok(SpendPolicy::After(DateTime::<Utc>::decode_async(d).await?))
434
+ }
435
+ POLICY_PUBLIC_KEY_PREFIX => {
436
+ Ok(SpendPolicy::PublicKey(PublicKey::decode_async(d).await?))
437
+ }
438
+ POLICY_HASH_PREFIX => Ok(SpendPolicy::Hash(Hash256::decode_async(d).await?)),
439
+ POLICY_THRESHOLD_PREFIX => {
440
+ let of: u8 = u8::decode_async(d).await?;
441
+ let n = u8::decode_async(d).await?;
442
+ let mut policies = Vec::with_capacity(n as usize);
443
+ while policies.len() < n as usize {
444
+ policies.push(decode_policy(d).await?);
445
+ }
446
+ Ok(SpendPolicy::Threshold(of, policies))
447
+ }
448
+ POLICY_OPAQUE_PREFIX => Ok(SpendPolicy::Opaque(Address::decode_async(d).await?)),
449
+ #[allow(deprecated)]
450
+ POLICY_UNLOCK_CONDITIONS_PREFIX => Ok(SpendPolicy::UnlockConditions(
451
+ UnlockConditions::decode_async(d).await?,
452
+ )),
453
+ _ => Err(EncodingError::InvalidValue(format!(
454
+ "invalid policy type: {policy_type}"
455
+ ))
456
+ .into()),
457
+ }
458
+ }
459
+ let policy_version = u8::decode_async(d).await?;
460
+ if policy_version != 1 {
461
+ return Err(EncodingError::InvalidValue(format!(
462
+ "invalid policy version: {policy_version}"
463
+ ))
464
+ .into());
465
+ }
466
+ decode_policy(d).await
467
+ }
468
+ }
469
+
470
+ #[derive(
471
+ Debug,
472
+ PartialEq,
473
+ Serialize,
474
+ Deserialize,
475
+ SiaEncode,
476
+ SiaDecode,
477
+ AsyncSiaEncode,
478
+ AsyncSiaDecode,
479
+ Clone,
480
+ )]
481
+ /// A policy that has been satisfied by a set of preimages and signatures.
482
+ pub struct SatisfiedPolicy {
483
+ pub policy: SpendPolicy,
484
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
485
+ pub signatures: Vec<Signature>,
486
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
487
+ pub preimages: Vec<Hash256>,
488
+ }
489
+
490
+ #[cfg(test)]
491
+ mod tests {
492
+ use crate::{hash_256, public_key};
493
+
494
+ use super::*;
495
+
496
+ #[test]
497
+ fn test_address() {
498
+ let test_cases = vec![
499
+ (
500
+ SpendPolicy::PublicKey(PublicKey::new([
501
+ 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
502
+ 0, 0, 0, 0, 0, 0,
503
+ ])),
504
+ "55a7793237722c6df8222fd512063cb74228085ef1805c5184713648c159b919ac792fbad0e1",
505
+ ),
506
+ (
507
+ SpendPolicy::Above(100),
508
+ "c2fba9b9607c800e80d9284ed0fb9a55737ba1bbd67311d0d9242dd6376bed0c6ee355e814fa",
509
+ ),
510
+ (
511
+ SpendPolicy::After(DateTime::from_timestamp_secs(1433600000).unwrap()),
512
+ "5bdb96e33ffdf72619ad38bee57ad4db9eb242aeb2ee32020ba16179af5d46d501bd2011806b",
513
+ ),
514
+ (
515
+ SpendPolicy::Hash(Hash256::from([
516
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
517
+ 0, 0, 0, 0, 0, 0,
518
+ ])),
519
+ "1cc0fc4cde659333cf7e61971cc5025c5a6b4759c9d1c1d438227c3eb57d841512d4cd4ce620",
520
+ ),
521
+ (
522
+ SpendPolicy::Threshold(
523
+ 2,
524
+ vec![
525
+ SpendPolicy::PublicKey(PublicKey::new([
526
+ 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
527
+ 0, 0, 0, 0, 0, 0, 0, 0,
528
+ ])),
529
+ SpendPolicy::Above(100),
530
+ SpendPolicy::Threshold(
531
+ 2,
532
+ vec![
533
+ SpendPolicy::PublicKey(PublicKey::new([0; 32])),
534
+ SpendPolicy::After(
535
+ DateTime::from_timestamp_secs(1433600000).unwrap(),
536
+ ),
537
+ ],
538
+ ),
539
+ ],
540
+ ),
541
+ "30f516630280059c25ae92f3bf3c451be258ecd3249c43906e3d9dd9e86f2dc00ef5eeffc2c4",
542
+ ),
543
+ ];
544
+
545
+ for (policy, expected) in test_cases {
546
+ assert_eq!(policy.address().to_string(), expected);
547
+ }
548
+ }
549
+
550
+ #[test]
551
+ fn test_opaque_policy() {
552
+ let test_cases = vec![
553
+ SpendPolicy::above(100),
554
+ SpendPolicy::after(DateTime::from_timestamp_secs(100).unwrap()),
555
+ SpendPolicy::public_key(PublicKey::new([0; 32])),
556
+ SpendPolicy::hash(Hash256::from([0; 32])),
557
+ SpendPolicy::threshold(
558
+ 2,
559
+ vec![
560
+ SpendPolicy::public_key(PublicKey::new([0; 32])),
561
+ SpendPolicy::above(100),
562
+ ],
563
+ ),
564
+ SpendPolicy::threshold(
565
+ 2,
566
+ vec![
567
+ SpendPolicy::public_key(PublicKey::new([0; 32])),
568
+ SpendPolicy::above(100),
569
+ SpendPolicy::threshold(
570
+ 2,
571
+ vec![
572
+ SpendPolicy::public_key(PublicKey::new([0; 32])),
573
+ SpendPolicy::after(DateTime::from_timestamp_secs(100).unwrap()),
574
+ ],
575
+ ),
576
+ SpendPolicy::PublicKey(PublicKey::new([1; 32])),
577
+ ],
578
+ ),
579
+ ];
580
+
581
+ for (i, policy) in test_cases.into_iter().enumerate() {
582
+ let policy = policy.clone();
583
+ let address = policy.address();
584
+ let expected_address = address.to_string();
585
+ let opaque = SpendPolicy::Opaque(address);
586
+ assert_eq!(
587
+ opaque.address().to_string(),
588
+ expected_address,
589
+ "test case {i}"
590
+ );
591
+
592
+ if let SpendPolicy::Threshold(n, of) = policy {
593
+ // test that the address of opaque threshold policies is the
594
+ // same as the address of normal threshold policies
595
+ for j in 0..of.len() {
596
+ let mut of = of.clone();
597
+ of[j] = SpendPolicy::Opaque(of[j].address());
598
+ let opaque_policy = SpendPolicy::threshold(n, of);
599
+
600
+ assert_eq!(
601
+ opaque_policy.address().to_string(),
602
+ expected_address,
603
+ "test case {i}-{j}"
604
+ );
605
+ }
606
+ }
607
+ }
608
+ }
609
+
610
+ #[test]
611
+ fn test_policy_encoding() {
612
+ let test_cases = vec![
613
+ (
614
+ SpendPolicy::above(100),
615
+ "{\"type\":\"above\",\"policy\":100}",
616
+ "01016400000000000000",
617
+ ),
618
+ (
619
+ SpendPolicy::after(DateTime::from_timestamp_secs(100).unwrap()),
620
+ "{\"type\":\"after\",\"policy\":100}",
621
+ "01026400000000000000",
622
+ ),
623
+ (
624
+ SpendPolicy::public_key(PublicKey::new([1; 32])),
625
+ "{\"type\":\"pk\",\"policy\":\"ed25519:0101010101010101010101010101010101010101010101010101010101010101\"}",
626
+ "01030101010101010101010101010101010101010101010101010101010101010101",
627
+ ),
628
+ (
629
+ SpendPolicy::hash(Hash256::from([0; 32])),
630
+ "{\"type\":\"h\",\"policy\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
631
+ "01040000000000000000000000000000000000000000000000000000000000000000",
632
+ ),
633
+ (
634
+ SpendPolicy::threshold(
635
+ 2,
636
+ vec![
637
+ SpendPolicy::public_key(PublicKey::new([0; 32])),
638
+ SpendPolicy::above(100),
639
+ ],
640
+ ),
641
+ "{\"type\":\"thresh\",\"policy\":{\"n\":2,\"of\":[{\"policy\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"type\":\"pk\"},{\"policy\":100,\"type\":\"above\"}]}}",
642
+ "01050202030000000000000000000000000000000000000000000000000000000000000000016400000000000000",
643
+ ),
644
+ (
645
+ SpendPolicy::threshold(
646
+ 2,
647
+ vec![
648
+ SpendPolicy::public_key(PublicKey::new([0; 32])),
649
+ SpendPolicy::above(100),
650
+ SpendPolicy::threshold(
651
+ 2,
652
+ vec![
653
+ SpendPolicy::public_key(PublicKey::new([0; 32])),
654
+ SpendPolicy::after(DateTime::from_timestamp_secs(100).unwrap()),
655
+ ],
656
+ ),
657
+ SpendPolicy::PublicKey(PublicKey::new([0; 32])),
658
+ ],
659
+ ),
660
+ "{\"type\":\"thresh\",\"policy\":{\"n\":2,\"of\":[{\"policy\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"type\":\"pk\"},{\"policy\":100,\"type\":\"above\"},{\"policy\":{\"n\":2,\"of\":[{\"policy\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"type\":\"pk\"},{\"policy\":100,\"type\":\"after\"}]},\"type\":\"thresh\"},{\"policy\":\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"type\":\"pk\"}]}}",
661
+ "01050204030000000000000000000000000000000000000000000000000000000000000000016400000000000000050202030000000000000000000000000000000000000000000000000000000000000000026400000000000000030000000000000000000000000000000000000000000000000000000000000000",
662
+ ),
663
+ (
664
+ #[allow(deprecated)]
665
+ SpendPolicy::UnlockConditions(UnlockConditions {
666
+ timelock: 100,
667
+ signatures_required: 2,
668
+ public_keys: vec![
669
+ PublicKey::new([0; 32]).into(),
670
+ PublicKey::new([1; 32]).into(),
671
+ ],
672
+ }),
673
+ "{\"type\":\"uc\",\"policy\":{\"timelock\":100,\"publicKeys\":[\"ed25519:0000000000000000000000000000000000000000000000000000000000000000\",\"ed25519:0101010101010101010101010101010101010101010101010101010101010101\"],\"signaturesRequired\":2}}",
674
+ "010764000000000000000200000000000000656432353531390000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000065643235353139000000000000000000200000000000000001010101010101010101010101010101010101010101010101010101010101010200000000000000",
675
+ ),
676
+ ];
677
+
678
+ for (i, (policy, json, binary)) in test_cases.iter().enumerate() {
679
+ let serialized_json = serde_json::to_string(&policy)
680
+ .unwrap_or_else(|e| panic!("failed to serialize json in test case {i}: {e}"));
681
+ assert_eq!(serialized_json, *json, "test case {i}");
682
+ let deserialized_json: SpendPolicy = serde_json::from_str(json)
683
+ .unwrap_or_else(|e| panic!("failed to deserialize json in test case {i}: {e}"));
684
+ assert_eq!(deserialized_json, *policy, "test case {i}");
685
+
686
+ let mut serialized_binary = Vec::new();
687
+ policy
688
+ .encode(&mut serialized_binary)
689
+ .unwrap_or_else(|e| panic!("failed to serialize binary in test case {i}: {e}"));
690
+ assert_eq!(
691
+ hex::encode(serialized_binary.clone()),
692
+ *binary,
693
+ "test case {i}"
694
+ );
695
+
696
+ let deserialized_binary = SpendPolicy::decode(&mut &serialized_binary[..])
697
+ .unwrap_or_else(|e| panic!("failed to deserialize binary in test case {i}: {e}"));
698
+ assert_eq!(deserialized_binary, *policy, "test case {i}");
699
+ }
700
+ }
701
+
702
+ #[test]
703
+ fn test_satisfied_policy_encoding() {
704
+ struct TestCase {
705
+ policy: SatisfiedPolicy,
706
+ json: &'static str,
707
+ binary: &'static str,
708
+ }
709
+ let test_cases = vec![
710
+ TestCase{
711
+ policy: SatisfiedPolicy{
712
+ policy: SpendPolicy::public_key(public_key!("ed25519:3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29")),
713
+ signatures: vec!["867c405977a52caf455221d49ea6242584221ab7f6b1a3e5a7e515d1f4027852e641330366fbd93950e5d15d7153c288a7d96462db912235129725bf13b2a30c".parse().unwrap()],
714
+ preimages: vec![],
715
+ },
716
+ json: "{\"policy\":{\"type\":\"pk\",\"policy\":\"ed25519:3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29\"},\"signatures\":[\"867c405977a52caf455221d49ea6242584221ab7f6b1a3e5a7e515d1f4027852e641330366fbd93950e5d15d7153c288a7d96462db912235129725bf13b2a30c\"]}",
717
+ binary: "01033b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da290100000000000000867c405977a52caf455221d49ea6242584221ab7f6b1a3e5a7e515d1f4027852e641330366fbd93950e5d15d7153c288a7d96462db912235129725bf13b2a30c0000000000000000",
718
+ },
719
+ TestCase{
720
+ policy: SatisfiedPolicy{
721
+ policy: SpendPolicy::threshold(1, vec![
722
+ SpendPolicy::public_key(public_key!("ed25519:3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29")),
723
+ SpendPolicy::hash(hash_256!("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8")),
724
+ ]),
725
+ signatures: vec!["867c405977a52caf455221d49ea6242584221ab7f6b1a3e5a7e515d1f4027852e641330366fbd93950e5d15d7153c288a7d96462db912235129725bf13b2a30c".parse().unwrap()],
726
+ preimages: vec![hash_256!("0102030000000000000000000000000000000000000000000000000000000000")],
727
+ },
728
+ json: "{\"policy\":{\"type\":\"thresh\",\"policy\":{\"n\":1,\"of\":[{\"policy\":\"ed25519:3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29\",\"type\":\"pk\"},{\"policy\":\"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8\",\"type\":\"h\"}]}},\"signatures\":[\"867c405977a52caf455221d49ea6242584221ab7f6b1a3e5a7e515d1f4027852e641330366fbd93950e5d15d7153c288a7d96462db912235129725bf13b2a30c\"],\"preimages\":[\"0102030000000000000000000000000000000000000000000000000000000000\"]}",
729
+ binary: "01050102033b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29040e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a80100000000000000867c405977a52caf455221d49ea6242584221ab7f6b1a3e5a7e515d1f4027852e641330366fbd93950e5d15d7153c288a7d96462db912235129725bf13b2a30c01000000000000000102030000000000000000000000000000000000000000000000000000000000",
730
+ },
731
+ TestCase{
732
+ policy: SatisfiedPolicy{
733
+ policy: SpendPolicy::hash(hash_256!("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8")),
734
+ signatures: vec![],
735
+ preimages: vec![hash_256!("0405060000000000000000000000000000000000000000000000000000000000")],
736
+ },
737
+ json: "{\"policy\":{\"type\":\"h\",\"policy\":\"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8\"},\"preimages\":[\"0405060000000000000000000000000000000000000000000000000000000000\"]}",
738
+ binary: "01040e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8000000000000000001000000000000000405060000000000000000000000000000000000000000000000000000000000",
739
+ },
740
+ TestCase{
741
+ policy: SatisfiedPolicy{
742
+ policy: SpendPolicy::public_key(public_key!("ed25519:3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29")),
743
+ signatures: vec![],
744
+ preimages: vec![],
745
+ },
746
+ json: "{\"policy\":{\"type\":\"pk\",\"policy\":\"ed25519:3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29\"}}",
747
+ binary: "01033b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da2900000000000000000000000000000000",
748
+ },
749
+ TestCase{
750
+ policy: SatisfiedPolicy{
751
+ policy: SpendPolicy::hash(hash_256!("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8")),
752
+ signatures: vec![],
753
+ preimages: vec![],
754
+ },
755
+ json: "{\"policy\":{\"type\":\"h\",\"policy\":\"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8\"}}",
756
+ binary: "01040e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a800000000000000000000000000000000",
757
+ }
758
+ ];
759
+
760
+ for tc in test_cases {
761
+ let mut serialized = Vec::new();
762
+ tc.policy.encode(&mut serialized).unwrap();
763
+ assert_eq!(
764
+ hex::encode(serialized.clone()),
765
+ tc.binary,
766
+ "binary serialization failed"
767
+ );
768
+ let deserialized = SatisfiedPolicy::decode(&mut &serialized[..]).unwrap();
769
+ assert_eq!(deserialized, tc.policy, "binary deserialization failed");
770
+
771
+ // json
772
+ let serialized = serde_json::to_string(&tc.policy).unwrap();
773
+ assert_eq!(serialized, tc.json, "json serialization failed");
774
+ let deserialized: SatisfiedPolicy = serde_json::from_str(tc.json).unwrap();
775
+ assert_eq!(deserialized, tc.policy, "json deserialization failed");
776
+ }
777
+ }
778
+ }