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,146 @@
1
+ use crate::encoding::SiaEncodable;
2
+ use crate::encoding_async::{AsyncSiaDecodable, AsyncSiaDecode, AsyncSiaEncodable, AsyncSiaEncode};
3
+ use crate::types::v2::NetAddress;
4
+ use blake2b_simd::Params;
5
+ use chrono::{DateTime, Utc};
6
+ use serde::{Deserialize, Serialize};
7
+
8
+ use crate::signing::{PrivateKey, PublicKey, Signature};
9
+ use crate::types::{Address, Currency, Hash256};
10
+
11
+ pub const SEGMENT_SIZE: usize = 64;
12
+ pub const SECTOR_SIZE: usize = 1 << 22;
13
+ pub const LEAVES_PER_SECTOR: usize = SECTOR_SIZE / SEGMENT_SIZE;
14
+
15
+ /// Represents a host in the Sia network. The
16
+ /// addresses can be used to connect to the host.
17
+ #[derive(Debug, PartialEq, Serialize, Deserialize)]
18
+ #[serde(rename_all = "camelCase")]
19
+ pub struct Host {
20
+ pub public_key: PublicKey,
21
+ pub addresses: Vec<NetAddress>,
22
+ pub country_code: String,
23
+ pub latitude: f64,
24
+ pub longitude: f64,
25
+ pub good_for_upload: bool,
26
+ }
27
+
28
+ /// Contains the prices and parameters of a host.
29
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, AsyncSiaEncode, AsyncSiaDecode)]
30
+ #[serde(rename_all = "camelCase")]
31
+ pub struct HostPrices {
32
+ /// The price of forming a new contract with the host.
33
+ pub contract_price: Currency,
34
+ /// The collateral per byte per block the host will
35
+ /// risk for stored data.
36
+ pub collateral: Currency,
37
+ /// The cost of storing a sector on the host per byte per block.
38
+ pub storage_price: Currency,
39
+ /// The cost of uploading data to the host per byte.
40
+ pub ingress_price: Currency,
41
+ /// The cost of downloading data from the host per byte.
42
+ pub egress_price: Currency,
43
+ /// The cost to remove a sector from a contract.
44
+ pub free_sector_price: Currency,
45
+ /// The current height of the host's blockchain.
46
+ pub tip_height: u64,
47
+ /// The time until which the prices are valid.
48
+ pub valid_until: DateTime<Utc>,
49
+
50
+ pub signature: Signature,
51
+ }
52
+
53
+ impl HostPrices {
54
+ /// Computes the signature hash for the host prices.
55
+ pub fn sig_hash(&self) -> Hash256 {
56
+ let mut state = Params::new().hash_length(32).to_state();
57
+ self.contract_price.encode(&mut state).unwrap();
58
+ self.collateral.encode(&mut state).unwrap();
59
+ self.storage_price.encode(&mut state).unwrap();
60
+ self.ingress_price.encode(&mut state).unwrap();
61
+ self.egress_price.encode(&mut state).unwrap();
62
+ self.free_sector_price.encode(&mut state).unwrap();
63
+ self.tip_height.encode(&mut state).unwrap();
64
+ self.valid_until.encode(&mut state).unwrap();
65
+ state.finalize().into()
66
+ }
67
+
68
+ /// Checks if the prices are valid for the given host key and timestamp.
69
+ pub fn is_valid(&self, host_key: &PublicKey, timestamp: DateTime<Utc>) -> bool {
70
+ self.valid_until > timestamp
71
+ && self.tip_height > 0
72
+ && host_key.verify(self.sig_hash().as_ref(), &self.signature)
73
+ }
74
+ }
75
+
76
+ /// Contains the settings of a host, including its prices and other parameters.
77
+ #[derive(Debug, PartialEq, Serialize, Deserialize, AsyncSiaEncode, AsyncSiaDecode)]
78
+ #[serde(rename_all = "camelCase")]
79
+ pub struct HostSettings {
80
+ /// The version of the protocol the host is using.
81
+ pub protocol_version: [u8; 3],
82
+ /// The current release the host is running.
83
+ pub release: String,
84
+ /// The wallet address of the host to use for contract payments.
85
+ pub wallet_address: Address,
86
+ /// If the host is accepting new contracts.
87
+ pub accepting_contracts: bool,
88
+ /// The maximum amount of collateral that the host will accept for a
89
+ /// single contract.
90
+ pub max_collateral: Currency,
91
+ /// The maximum duration, in blocks, that the host will accept for a contract.
92
+ pub max_contract_duration: u64,
93
+ /// The amount of storage, in sectors, that the host has available.
94
+ pub remaining_storage: u64,
95
+ /// The total amount of storage, in sectors, that the host is offering.
96
+ pub total_storage: u64,
97
+ /// The current prices of the host
98
+ pub prices: HostPrices,
99
+ }
100
+
101
+ /// An account token is used to pay for RPC calls that do not
102
+ /// require a contract.
103
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, AsyncSiaEncode, AsyncSiaDecode)]
104
+ #[serde(rename_all = "camelCase")]
105
+ pub struct AccountToken {
106
+ pub host_key: PublicKey,
107
+ pub account: PublicKey,
108
+ pub valid_until: DateTime<Utc>,
109
+
110
+ pub signature: Signature,
111
+ }
112
+
113
+ impl AccountToken {
114
+ fn compute_sig_hash(
115
+ host_key: &PublicKey,
116
+ account: &PublicKey,
117
+ valid_until: &DateTime<Utc>,
118
+ ) -> Hash256 {
119
+ let mut state = Params::new().hash_length(32).to_state();
120
+ host_key.encode(&mut state).unwrap();
121
+ account.encode(&mut state).unwrap();
122
+ valid_until.encode(&mut state).unwrap();
123
+ state.finalize().into()
124
+ }
125
+
126
+ pub fn new(account_key: &PrivateKey, host_key: PublicKey) -> Self {
127
+ let expiration_time = chrono::Utc::now() + chrono::Duration::minutes(5);
128
+ let sig_hash =
129
+ Self::compute_sig_hash(&host_key, &account_key.public_key(), &expiration_time);
130
+ AccountToken {
131
+ host_key,
132
+ account: account_key.public_key(),
133
+ valid_until: expiration_time,
134
+
135
+ signature: account_key.sign(sig_hash.as_ref()),
136
+ }
137
+ }
138
+ }
139
+
140
+ /// An AccountDeposit is an amount of Siacoin to be deposited into an account.
141
+ #[derive(Debug, PartialEq, Serialize, Deserialize, AsyncSiaEncode, AsyncSiaDecode)]
142
+ #[serde(rename_all = "camelCase")]
143
+ pub struct AccountDeposit {
144
+ pub account: PublicKey,
145
+ pub amount: Currency,
146
+ }
@@ -0,0 +1,7 @@
1
+ mod merkle;
2
+ mod rpc;
3
+ mod types;
4
+
5
+ pub use merkle::*;
6
+ pub use rpc::*;
7
+ pub use types::*;
@@ -0,0 +1,278 @@
1
+ use std::fmt::Display;
2
+
3
+ use crate::blake2::Blake2b256;
4
+ use crate::signing::PrivateKey;
5
+ use bip39::{Error as MnemonicError, Language, Mnemonic};
6
+ use sha2::Digest;
7
+ use thiserror::Error;
8
+ use zeroize::ZeroizeOnDrop;
9
+
10
+ #[derive(ZeroizeOnDrop)]
11
+ pub struct Seed {
12
+ seed: [u8; 16],
13
+ entropy: [u8; 32],
14
+ }
15
+
16
+ #[derive(Debug, PartialEq, Error)]
17
+ pub enum SeedError {
18
+ #[error("failed to parse recovery phrase")]
19
+ MnemonicError(#[from] MnemonicError),
20
+ #[error("invalid length of entropy, must be 16 bytes")]
21
+ InvalidLength,
22
+ }
23
+
24
+ impl Seed {
25
+ pub fn new(s: &str) -> Result<Self, SeedError> {
26
+ let m = Mnemonic::parse_in(Language::English, s)?;
27
+ let buf = m.to_entropy();
28
+ if buf.len() != 16 {
29
+ return Err(SeedError::InvalidLength);
30
+ }
31
+ let mut seed = [0u8; 16];
32
+ seed.copy_from_slice(&buf);
33
+ Ok(Self::from_seed(seed))
34
+ }
35
+
36
+ pub fn from_seed(seed: [u8; 16]) -> Self {
37
+ let mut h = Blake2b256::new();
38
+ h.update(seed);
39
+
40
+ let entropy: [u8; 32] = h.finalize().into();
41
+ Seed { seed, entropy }
42
+ }
43
+
44
+ pub fn entropy(&self) -> &[u8] {
45
+ &self.entropy
46
+ }
47
+
48
+ /// Derive a private key from the seed
49
+ pub fn private_key(&self, index: u64) -> PrivateKey {
50
+ let mut h = Blake2b256::new();
51
+ h.update(self.entropy());
52
+ h.update(index.to_le_bytes());
53
+ let hash: [u8; 32] = h.finalize().into();
54
+
55
+ PrivateKey::from_seed(&hash)
56
+ }
57
+ }
58
+
59
+ impl Display for Seed {
60
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61
+ let m = Mnemonic::from_entropy(&self.seed).expect("invalid seed");
62
+ write!(f, "{}", m)
63
+ }
64
+ }
65
+
66
+ #[cfg(test)]
67
+ mod tests {
68
+ use super::*;
69
+
70
+ #[test]
71
+ fn test_seed_from_entropy() {
72
+ let test_cases = vec![
73
+ (
74
+ [
75
+ 23, 154, 249, 239, 129, 81, 216, 147, 144, 163, 207, 136, 238, 88, 11, 253,
76
+ ],
77
+ "bleak style know actor budget endorse dream ketchup material index actual wide",
78
+ ),
79
+ (
80
+ [
81
+ 125, 190, 141, 81, 70, 235, 204, 217, 162, 19, 65, 96, 237, 125, 157, 255,
82
+ ],
83
+ "laundry virtual february miss rubber holiday marriage habit genius hip guess yard",
84
+ ),
85
+ (
86
+ [
87
+ 56, 47, 30, 87, 122, 143, 19, 221, 189, 249, 105, 45, 161, 38, 172, 91,
88
+ ],
89
+ "deal jump noise vital van uphold wave coffee color ankle prison repeat",
90
+ ),
91
+ (
92
+ [
93
+ 68, 205, 7, 92, 32, 16, 228, 222, 144, 102, 94, 12, 179, 15, 67, 251,
94
+ ],
95
+ "dynamic habit strike dizzy atom hungry dose slim arrow observe special wash",
96
+ ),
97
+ (
98
+ [
99
+ 113, 45, 77, 233, 42, 222, 26, 5, 158, 171, 102, 114, 10, 7, 178, 19,
100
+ ],
101
+ "illness heavy kid fiber ticket actress kingdom holiday improve expand uncle chest",
102
+ ),
103
+ (
104
+ [
105
+ 189, 194, 141, 142, 240, 157, 147, 143, 61, 104, 167, 223, 33, 191, 95, 226,
106
+ ],
107
+ "saddle behave glove thrive summer shy volcano belt tennis assume subject series",
108
+ ),
109
+ (
110
+ [
111
+ 156, 21, 48, 230, 15, 181, 230, 46, 105, 106, 91, 163, 205, 77, 45, 150,
112
+ ],
113
+ "orchard praise define buyer fury blame pizza enter phrase heavy enter collect",
114
+ ),
115
+ (
116
+ [
117
+ 14, 38, 59, 189, 192, 248, 53, 60, 139, 36, 93, 58, 42, 156, 174, 238,
118
+ ],
119
+ "athlete crack urge limit local oxygen clutch merry demand female close talent",
120
+ ),
121
+ (
122
+ [
123
+ 141, 252, 20, 155, 232, 12, 56, 225, 252, 11, 92, 219, 9, 189, 23, 179,
124
+ ],
125
+ "mistake thing cheap source seminar ill usual high swallow evil echo grid",
126
+ ),
127
+ (
128
+ [
129
+ 25, 192, 89, 200, 149, 97, 136, 115, 38, 103, 19, 229, 88, 165, 62, 169,
130
+ ],
131
+ "border actress impulse client blush define office tiny torch share exile famous",
132
+ ),
133
+ (
134
+ [
135
+ 255, 43, 106, 70, 38, 84, 73, 72, 184, 0, 154, 228, 158, 156, 171, 32,
136
+ ],
137
+ "you forget muscle erosion duty picture theme battle tonight visual client double",
138
+ ),
139
+ (
140
+ [
141
+ 222, 237, 244, 22, 242, 80, 27, 122, 27, 91, 110, 101, 44, 200, 107, 151,
142
+ ],
143
+ "ten hurry aisle tool accuse rug hope horse gown green brain comfort",
144
+ ),
145
+ (
146
+ [
147
+ 96, 74, 173, 157, 208, 13, 130, 1, 168, 248, 254, 178, 92, 220, 59, 233,
148
+ ],
149
+ "gate fever guess parade subway absorb physical cabin rather tragic auction spread",
150
+ ),
151
+ (
152
+ [
153
+ 176, 115, 232, 168, 178, 206, 187, 177, 117, 105, 82, 1, 211, 62, 184, 132,
154
+ ],
155
+ "race palm clay grain two suffer stick clean achieve okay purchase anger",
156
+ ),
157
+ (
158
+ [
159
+ 162, 226, 161, 55, 247, 115, 251, 40, 6, 205, 151, 77, 203, 35, 63, 198,
160
+ ],
161
+ "pepper bench evil upon distance neglect brass real evidence flip soup mind",
162
+ ),
163
+ (
164
+ [
165
+ 199, 68, 177, 121, 94, 197, 135, 255, 140, 56, 181, 119, 99, 179, 124, 65,
166
+ ],
167
+ "shrug cereal furnace rural flash zone couch birth jazz budget tenant lock",
168
+ ),
169
+ (
170
+ [
171
+ 149, 139, 33, 29, 170, 228, 57, 90, 209, 219, 67, 202, 162, 198, 242, 138,
172
+ ],
173
+ "night flip electric fiction drum pulp electric half skirt bike royal benefit",
174
+ ),
175
+ (
176
+ [
177
+ 32, 138, 79, 90, 166, 241, 239, 197, 108, 63, 107, 211, 140, 3, 80, 129,
178
+ ],
179
+ "calm fame stove evil bus tired rail uniform squeeze gas stage acoustic",
180
+ ),
181
+ (
182
+ [
183
+ 231, 198, 66, 33, 240, 199, 105, 69, 236, 87, 87, 250, 128, 220, 227, 145,
184
+ ],
185
+ "treat craft mask thunder isolate pepper rally turtle whisper alone decline card",
186
+ ),
187
+ (
188
+ [
189
+ 130, 4, 98, 53, 124, 85, 66, 215, 112, 229, 188, 157, 95, 195, 49, 201,
190
+ ],
191
+ "link cart minute weather feature hill seminar resource outer wrap small narrow",
192
+ ),
193
+ (
194
+ [
195
+ 228, 84, 238, 177, 92, 231, 129, 253, 139, 4, 83, 68, 252, 160, 139, 22,
196
+ ],
197
+ "tone polar proof right job yard clown media eager topic carpet cluster",
198
+ ),
199
+ (
200
+ [
201
+ 227, 39, 94, 239, 75, 67, 63, 122, 188, 27, 58, 162, 126, 135, 55, 250,
202
+ ],
203
+ "tobacco depend rookie notable crop run vacant guard pen vintage social visual",
204
+ ),
205
+ (
206
+ [
207
+ 228, 96, 128, 106, 248, 223, 176, 232, 179, 247, 6, 219, 81, 173, 27, 141,
208
+ ],
209
+ "tongue advice boy vast wild inmate sound this swap miracle eight bottom",
210
+ ),
211
+ (
212
+ [
213
+ 204, 38, 228, 251, 95, 106, 131, 62, 103, 254, 162, 86, 97, 48, 254, 222,
214
+ ],
215
+ "slow damp disagree salute popular palace paper stairs filter another distance sadness",
216
+ ),
217
+ ];
218
+
219
+ for (entropy, expected) in test_cases {
220
+ let seed = Seed::from_seed(entropy);
221
+ assert_eq!(seed.to_string(), expected.to_string());
222
+ }
223
+ }
224
+
225
+ #[test]
226
+ fn test_seed_private_key() {
227
+ const PHRASE: &str =
228
+ "wealth salon venue abstract blossom hollow south over accuse bunker guide saddle";
229
+ let test_addresses = vec![
230
+ (0, hex::decode("e313a1aa2dbe411b5335ced5592e87cb002f47a874e27e9cb90eab285c675e366d29b52b7b312fb5e4f657afd0105d3d6dcc5c326131a033597501d25612789f").unwrap()),
231
+ (1, hex::decode("0a909bf1d36c876cb776b81e19c8b4a1351c644e329db3be07f6dfce59b75f4d3fa53cfea6763b07cc4202a0ba36574d99fa6ca3f807dbff2f2266c4d0a0a76d").unwrap()),
232
+ (2, hex::decode("866b40a6ee117ab8e65ee0772ca4e463e98edbf0793beae08a784745e7f10554294324450371bb263bc02c4536a04afa355ca490ef6481fd682dfd44bdb0f464").unwrap()),
233
+ (3, hex::decode("f713e2a9cc2415d7069d136c73dd3a67c5f2a63cc04f1106b980d6d6cd816f6bf710d69b256ae23f4b28d1f02f714fed04ea2c9268598835713eec36697bf179").unwrap()),
234
+ (4, hex::decode("433b5bf2c3ec44895af7299148ba38deaa7324c5146821fcef407708abc211bcb12b2a480977ffdc4c3801752b0e2bee06219311b7bdce80189be961f47d7ac9").unwrap()),
235
+ (5, hex::decode("48a4765ece4d7e6b12f4f8b20caaca4b2249654ada2b9d0d31d855517244b1ed8850f06b52e7ce6b5ea061ac6b69f3febb3fc96e58c590c975300fb20f317dcc").unwrap()),
236
+ (6, hex::decode("2de36d94f299ab39511e9eb3fe0cf5cc989b25e2943ca9c3a87ac592831791d76b0ee63d4be3b5296fe3961150b6bc3dd5f0acc56235fb8a62143a7eb73bdaa7").unwrap()),
237
+ (7, hex::decode("ecbd64189b9429583ad62173035cf3680238e5d90727220f55d466e88dc631b70299cbd2b777df0e62099f3f5f913692d022a3faabd461a2933754ec3aa35c21").unwrap()),
238
+ (8, hex::decode("5e836458fccb204dfe0e300c66ca2c47ad7efe9f835cda99d1a3cf22cf642634d53903b6ba22cf84adcae25f3d27d90323017ff793115b559df26fc0a4450cf4").unwrap()),
239
+ (9, hex::decode("9abd3e40d6d3b10d36966cec65861d7f08c6aa7f2d2845b0e9f10e15cc9e9f28eb63b79a7719c1a8323fe2d3da06d121ccbda1342d9f0913860f5e0817af1390").unwrap()),
240
+ (4294967295, hex::decode("71945cbc310c189f01fe8727a0060c007528aa0fd4812e4c5c7aa8b0e518906fc7dcf5e7623152e310ed440b8abf02e02fbead45553f13b3e8a7bea78a16d1b8").unwrap()),
241
+ (4294967296, hex::decode("41602321666c7ba93b05729208897ddf89940afdf02c38ebbd88f0a4906839232cd126afb5ccaef91ab77fcc27d076d94c5d152729bde3794bcd03226679889c").unwrap()),
242
+ (18446744073709551615, hex::decode("c03b2570cb69e300cd4ccbe0c4d8ee7b8ccfad7383f10aa2df52a4a9d05ab843d39fbd56458e94d711061748a051d434d2200e1af71df56070d2df0883453b2c").unwrap()),
243
+ ];
244
+
245
+ let seed = Seed::new(PHRASE).unwrap();
246
+ for (i, expected) in test_addresses {
247
+ let pk = seed.private_key(i);
248
+ assert_eq!(pk.as_ref(), expected, "index {i}");
249
+ }
250
+ }
251
+
252
+ #[test]
253
+ fn test_seed_public_key() {
254
+ const PHRASE: &str =
255
+ "wealth salon venue abstract blossom hollow south over accuse bunker guide saddle";
256
+ let test_addresses = vec![
257
+ (0, hex::decode("e313a1aa2dbe411b5335ced5592e87cb002f47a874e27e9cb90eab285c675e366d29b52b7b312fb5e4f657afd0105d3d6dcc5c326131a033597501d25612789f").unwrap()),
258
+ (1, hex::decode("0a909bf1d36c876cb776b81e19c8b4a1351c644e329db3be07f6dfce59b75f4d3fa53cfea6763b07cc4202a0ba36574d99fa6ca3f807dbff2f2266c4d0a0a76d").unwrap()),
259
+ (2, hex::decode("866b40a6ee117ab8e65ee0772ca4e463e98edbf0793beae08a784745e7f10554294324450371bb263bc02c4536a04afa355ca490ef6481fd682dfd44bdb0f464").unwrap()),
260
+ (3, hex::decode("f713e2a9cc2415d7069d136c73dd3a67c5f2a63cc04f1106b980d6d6cd816f6bf710d69b256ae23f4b28d1f02f714fed04ea2c9268598835713eec36697bf179").unwrap()),
261
+ (4, hex::decode("433b5bf2c3ec44895af7299148ba38deaa7324c5146821fcef407708abc211bcb12b2a480977ffdc4c3801752b0e2bee06219311b7bdce80189be961f47d7ac9").unwrap()),
262
+ (5, hex::decode("48a4765ece4d7e6b12f4f8b20caaca4b2249654ada2b9d0d31d855517244b1ed8850f06b52e7ce6b5ea061ac6b69f3febb3fc96e58c590c975300fb20f317dcc").unwrap()),
263
+ (6, hex::decode("2de36d94f299ab39511e9eb3fe0cf5cc989b25e2943ca9c3a87ac592831791d76b0ee63d4be3b5296fe3961150b6bc3dd5f0acc56235fb8a62143a7eb73bdaa7").unwrap()),
264
+ (7, hex::decode("ecbd64189b9429583ad62173035cf3680238e5d90727220f55d466e88dc631b70299cbd2b777df0e62099f3f5f913692d022a3faabd461a2933754ec3aa35c21").unwrap()),
265
+ (8, hex::decode("5e836458fccb204dfe0e300c66ca2c47ad7efe9f835cda99d1a3cf22cf642634d53903b6ba22cf84adcae25f3d27d90323017ff793115b559df26fc0a4450cf4").unwrap()),
266
+ (9, hex::decode("9abd3e40d6d3b10d36966cec65861d7f08c6aa7f2d2845b0e9f10e15cc9e9f28eb63b79a7719c1a8323fe2d3da06d121ccbda1342d9f0913860f5e0817af1390").unwrap()),
267
+ (4294967295, hex::decode("71945cbc310c189f01fe8727a0060c007528aa0fd4812e4c5c7aa8b0e518906fc7dcf5e7623152e310ed440b8abf02e02fbead45553f13b3e8a7bea78a16d1b8").unwrap()),
268
+ (4294967296, hex::decode("41602321666c7ba93b05729208897ddf89940afdf02c38ebbd88f0a4906839232cd126afb5ccaef91ab77fcc27d076d94c5d152729bde3794bcd03226679889c").unwrap()),
269
+ (18446744073709551615, hex::decode("c03b2570cb69e300cd4ccbe0c4d8ee7b8ccfad7383f10aa2df52a4a9d05ab843d39fbd56458e94d711061748a051d434d2200e1af71df56070d2df0883453b2c").unwrap()),
270
+ ];
271
+
272
+ let seed = Seed::new(PHRASE).unwrap();
273
+ for (i, expected) in test_addresses {
274
+ let pk = seed.private_key(i).public_key();
275
+ assert_eq!(pk.as_ref(), &expected[32..], "index {i}");
276
+ }
277
+ }
278
+ }
@@ -0,0 +1,236 @@
1
+ use core::fmt;
2
+
3
+ use crate::encoding::{self, SiaDecodable, SiaDecode, SiaEncodable, SiaEncode};
4
+ use crate::encoding_async::{AsyncSiaDecodable, AsyncSiaDecode, AsyncSiaEncodable, AsyncSiaEncode};
5
+ use crate::types::{Hash256, HexParseError};
6
+ use ed25519_dalek::{Signature as ED25519Signature, Signer, SigningKey, Verifier, VerifyingKey};
7
+ use serde::de::Error;
8
+ use serde::{Deserialize, Serialize};
9
+ use zeroize::ZeroizeOnDrop;
10
+
11
+ /// An ed25519 public key that can be used to verify a signature
12
+ #[derive(
13
+ Debug, Eq, Hash, PartialEq, Clone, Copy, SiaEncode, SiaDecode, AsyncSiaDecode, AsyncSiaEncode,
14
+ )]
15
+ pub struct PublicKey([u8; 32]);
16
+
17
+ impl PublicKey {
18
+ const PREFIX: &'static str = "ed25519:";
19
+ }
20
+
21
+ impl Serialize for PublicKey {
22
+ fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
23
+ String::serialize(&self.to_string(), serializer)
24
+ }
25
+ }
26
+
27
+ impl<'de> Deserialize<'de> for PublicKey {
28
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
29
+ where
30
+ D: serde::Deserializer<'de>,
31
+ {
32
+ let s = String::deserialize(deserializer)?;
33
+ let result = s.parse().map_err(|e| Error::custom(format!("{e:?}")))?;
34
+ Ok(result)
35
+ }
36
+ }
37
+
38
+ impl std::str::FromStr for PublicKey {
39
+ type Err = crate::types::HexParseError;
40
+
41
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
42
+ let s = s
43
+ .strip_prefix(Self::PREFIX)
44
+ .ok_or(HexParseError::MissingPrefix)?;
45
+ let mut pk = [0; 32];
46
+ hex::decode_to_slice(s, &mut pk)?;
47
+ Ok(Self::new(pk))
48
+ }
49
+ }
50
+
51
+ impl fmt::Display for PublicKey {
52
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
53
+ write!(f, "{}{}", Self::PREFIX, hex::encode(self.0))
54
+ }
55
+ }
56
+
57
+ impl PublicKey {
58
+ pub const fn new(buf: [u8; 32]) -> Self {
59
+ PublicKey(buf)
60
+ }
61
+
62
+ /// verifies a message against the signature using this public key
63
+ pub fn verify(&self, msg: &[u8], signature: &Signature) -> bool {
64
+ let pk = VerifyingKey::from_bytes(&self.0).unwrap();
65
+ pk.verify(msg, &ED25519Signature::from_bytes(signature.as_ref()))
66
+ .is_ok()
67
+ }
68
+ }
69
+
70
+ impl From<PublicKey> for [u8; 32] {
71
+ fn from(val: PublicKey) -> Self {
72
+ val.0
73
+ }
74
+ }
75
+
76
+ impl AsRef<[u8]> for PublicKey {
77
+ fn as_ref(&self) -> &[u8] {
78
+ &self.0
79
+ }
80
+ }
81
+
82
+ /// An ed25519 private key that can be used to sign a hash
83
+ #[derive(Debug, PartialEq, Clone, ZeroizeOnDrop)]
84
+ pub struct PrivateKey([u8; 64]);
85
+
86
+ impl PrivateKey {
87
+ pub fn from_seed(seed: &[u8; 32]) -> Self {
88
+ let sk = SigningKey::from_bytes(seed);
89
+ PrivateKey(sk.to_keypair_bytes())
90
+ }
91
+
92
+ pub fn public_key(&self) -> PublicKey {
93
+ let mut buf = [0u8; 32];
94
+ buf.copy_from_slice(&self.0[32..]);
95
+ PublicKey::new(buf)
96
+ }
97
+
98
+ pub fn sign(&self, h: &[u8]) -> Signature {
99
+ let sk = SigningKey::from_bytes(&self.0[..32].try_into().unwrap());
100
+ Signature::new(sk.sign(h).to_bytes())
101
+ }
102
+ }
103
+
104
+ impl AsRef<[u8]> for PrivateKey {
105
+ fn as_ref(&self) -> &[u8] {
106
+ &self.0
107
+ }
108
+ }
109
+
110
+ impl From<[u8; 64]> for PrivateKey {
111
+ fn from(key: [u8; 64]) -> Self {
112
+ PrivateKey(key)
113
+ }
114
+ }
115
+
116
+ impl From<Hash256> for PrivateKey {
117
+ fn from(hash: Hash256) -> Self {
118
+ PrivateKey::from_seed(hash.as_ref())
119
+ }
120
+ }
121
+
122
+ #[derive(Debug, Clone, PartialEq, Eq, SiaEncode, SiaDecode, AsyncSiaEncode, AsyncSiaDecode)]
123
+ pub struct Signature([u8; 64]);
124
+
125
+ impl Serialize for Signature {
126
+ fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
127
+ String::serialize(&hex::encode(self.0), serializer)
128
+ }
129
+ }
130
+
131
+ impl<'de> Deserialize<'de> for Signature {
132
+ fn deserialize<D>(deserializer: D) -> Result<Signature, D::Error>
133
+ where
134
+ D: serde::Deserializer<'de>,
135
+ {
136
+ let buf = hex::decode(String::deserialize(deserializer)?)
137
+ .map_err(|e| D::Error::custom(format!("{e:?}")))?;
138
+ if buf.len() != 64 {
139
+ return Err(D::Error::custom("Invalid signature length"));
140
+ }
141
+ Ok(Signature(buf.try_into().unwrap()))
142
+ }
143
+ }
144
+
145
+ impl Signature {
146
+ pub const fn new(sig: [u8; 64]) -> Self {
147
+ Signature(sig)
148
+ }
149
+
150
+ pub fn data(&self) -> &[u8] {
151
+ &self.0
152
+ }
153
+ }
154
+
155
+ impl Default for Signature {
156
+ fn default() -> Self {
157
+ Signature([0; 64])
158
+ }
159
+ }
160
+
161
+ impl AsRef<[u8; 64]> for Signature {
162
+ fn as_ref(&self) -> &[u8; 64] {
163
+ &self.0
164
+ }
165
+ }
166
+
167
+ impl From<[u8; 64]> for Signature {
168
+ fn from(buf: [u8; 64]) -> Self {
169
+ Signature(buf)
170
+ }
171
+ }
172
+
173
+ impl std::str::FromStr for Signature {
174
+ type Err = crate::types::HexParseError;
175
+
176
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
177
+ let data = hex::decode(s).map_err(HexParseError::HexError)?;
178
+ if data.len() != 64 {
179
+ return Err(HexParseError::InvalidLength(data.len()));
180
+ }
181
+
182
+ let mut sig = [0u8; 64];
183
+ sig.copy_from_slice(&data);
184
+ Ok(Signature(sig))
185
+ }
186
+ }
187
+
188
+ /// Converts a slice of bytes into a Signature.
189
+ /// # Errors
190
+ /// Returns an error if the slice is not exactly 64 bytes long.
191
+ impl TryFrom<&[u8]> for Signature {
192
+ type Error = encoding::Error;
193
+ fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
194
+ if value.len() != 64 {
195
+ return Err(encoding::Error::InvalidLength(value.len()));
196
+ }
197
+ let mut sig = [0u8; 64];
198
+ sig.copy_from_slice(value);
199
+ Ok(Signature(sig))
200
+ }
201
+ }
202
+
203
+ impl fmt::Display for Signature {
204
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205
+ write!(f, "{}", hex::encode(self.0))
206
+ }
207
+ }
208
+
209
+ #[cfg(test)]
210
+ mod tests {
211
+ use super::*;
212
+
213
+ #[test]
214
+ fn test_serialize_publickey() {
215
+ let public_key_str = "9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6";
216
+ let public_key = PublicKey::new(hex::decode(public_key_str).unwrap().try_into().unwrap());
217
+
218
+ // binary
219
+ let mut public_key_serialized = Vec::new();
220
+ public_key.encode(&mut public_key_serialized).unwrap();
221
+ assert_eq!(public_key_serialized, hex::decode(public_key_str).unwrap());
222
+ let public_key_deserialized =
223
+ PublicKey::decode(&mut public_key_serialized.as_slice()).unwrap();
224
+ assert_eq!(public_key_deserialized, public_key);
225
+
226
+ // json
227
+ let public_key_serialized = serde_json::to_string(&public_key).unwrap();
228
+ let public_key_deserialized: PublicKey =
229
+ serde_json::from_str(&public_key_serialized).unwrap();
230
+ assert_eq!(
231
+ public_key_serialized,
232
+ format!("\"ed25519:{public_key_str}\"")
233
+ );
234
+ assert_eq!(public_key_deserialized, public_key);
235
+ }
236
+ }