Skip to content

Commit 2a2ef2e

Browse files
authored
Merge pull request #9 from Rigidity/data-stores
Data stores
2 parents e970859 + 8f677aa commit 2a2ef2e

File tree

14 files changed

+302
-69
lines changed

14 files changed

+302
-69
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "chia-wallet-sdk"
3-
version = "0.3.0"
3+
version = "0.4.0"
44
edition = "2021"
55
license = "Apache-2.0"
66
description = "An unofficial SDK for building Chia wallets."

src/address.rs

Lines changed: 160 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,84 @@
1-
/// Parses an address into a puzzle hash.
2-
pub fn parse_address(address: &str) -> [u8; 32] {
3-
if let Ok(puzzle_hash) = hex::decode(strip_prefix(address)) {
4-
puzzle_hash.try_into().expect("invalid puzzle hash")
1+
use bech32::{u5, Variant};
2+
use hex::FromHexError;
3+
use thiserror::Error;
4+
5+
/// Errors you can get while trying to decode an address.
6+
#[derive(Error, Debug, Clone, PartialEq, Eq)]
7+
pub enum AddressError {
8+
/// The wrong HRP prefix was used.
9+
#[error("invalid prefix `{0}`")]
10+
InvalidPrefix(String),
11+
12+
/// The address was encoded as bech32, rather than bech32m.
13+
#[error("encoding is not bech32m")]
14+
InvalidFormat,
15+
16+
/// The data was not 32 bytes in length.
17+
#[error("wrong length, expected 32 bytes but found {0}")]
18+
WrongLength(usize),
19+
20+
/// An error occured while trying to decode the address.
21+
#[error("error when decoding address: {0}")]
22+
Decode(#[from] bech32::Error),
23+
}
24+
25+
/// Errors you can get while trying to decode a puzzle hash.
26+
#[derive(Error, Debug, Clone, PartialEq)]
27+
pub enum PuzzleHashError {
28+
/// The buffer was not 32 bytes in length.
29+
#[error("wrong length, expected 32 bytes but found {0}")]
30+
WrongLength(usize),
31+
32+
/// An error occured while trying to decode the puzzle hash.
33+
#[error("error when decoding puzzle hash: {0}")]
34+
Decode(#[from] FromHexError),
35+
}
36+
37+
/// Decodes a puzzle hash from hex, with or without a prefix.
38+
pub fn decode_puzzle_hash(puzzle_hash: &str) -> Result<[u8; 32], PuzzleHashError> {
39+
let data = hex::decode(strip_prefix(puzzle_hash))?;
40+
let length = data.len();
41+
data.try_into()
42+
.map_err(|_| PuzzleHashError::WrongLength(length))
43+
}
44+
45+
/// Encodes a puzzle hash into hex, with or without a prefix.
46+
pub fn encode_puzzle_hash(puzzle_hash: [u8; 32], include_0x: bool) -> String {
47+
if include_0x {
48+
format!("0x{}", hex::encode(puzzle_hash))
549
} else {
6-
let (_hrp, data, _variant) = bech32::decode(address).expect("invalid address");
7-
let puzzle_hash = bech32::convert_bits(&data, 5, 8, false).expect("invalid address data");
8-
puzzle_hash
9-
.try_into()
10-
.expect("invalid address puzzle hash encoding")
50+
hex::encode(puzzle_hash)
1151
}
1252
}
1353

54+
/// Decodes an address into a puzzle hash and HRP prefix.
55+
pub fn decode_address(address: &str) -> Result<([u8; 32], String), AddressError> {
56+
let (hrp, data, variant) = bech32::decode(address)?;
57+
if variant != Variant::Bech32m {
58+
return Err(AddressError::InvalidFormat);
59+
}
60+
61+
let data = bech32::convert_bits(&data, 5, 8, false)?;
62+
let length = data.len();
63+
let puzzle_hash = data
64+
.try_into()
65+
.map_err(|_| AddressError::WrongLength(length))?;
66+
67+
Ok((puzzle_hash, hrp))
68+
}
69+
70+
/// Encodes an address with a given HRP prefix.
71+
pub fn encode_address(puzzle_hash: [u8; 32], prefix: &str) -> Result<String, bech32::Error> {
72+
let data = bech32::convert_bits(&puzzle_hash, 8, 5, true)
73+
.unwrap()
74+
.into_iter()
75+
.map(u5::try_from_u8)
76+
.collect::<Result<Vec<_>, bech32::Error>>()?;
77+
bech32::encode(prefix, data, Variant::Bech32m)
78+
}
79+
1480
/// Removes the `0x` prefix from a puzzle hash in hex format.
15-
fn strip_prefix(puzzle_hash: &str) -> &str {
81+
pub fn strip_prefix(puzzle_hash: &str) -> &str {
1682
if let Some(puzzle_hash) = puzzle_hash.strip_prefix("0x") {
1783
puzzle_hash
1884
} else if let Some(puzzle_hash) = puzzle_hash.strip_prefix("0X") {
@@ -21,3 +87,87 @@ fn strip_prefix(puzzle_hash: &str) -> &str {
2187
puzzle_hash
2288
}
2389
}
90+
91+
#[cfg(test)]
92+
mod tests {
93+
use hex_literal::hex;
94+
95+
use super::*;
96+
97+
fn check_ph(expected: &str) {
98+
let expected = strip_prefix(expected);
99+
let puzzle_hash = decode_puzzle_hash(expected).unwrap();
100+
let actual = encode_puzzle_hash(puzzle_hash, false);
101+
assert_eq!(actual, expected);
102+
}
103+
104+
fn check_addr(expected: &str) {
105+
let (puzzle_hash, prefix) = decode_address(expected).unwrap();
106+
let actual = encode_address(puzzle_hash, &prefix).unwrap();
107+
assert_eq!(actual, expected);
108+
}
109+
110+
#[test]
111+
fn test_strip_prefix() {
112+
check_ph("0x2999682870bd24e7fd0ef6324c69794ff93fc41b016777d2edd5ea8575bdaa31");
113+
check_ph("0x99619cc6888f1bd30acd6e8c1f4065dafeba2246bfc3465cddda4e6656083791");
114+
check_ph("0X7cc6494dd96d32c97b5f6ba77caae269acd6c86593ada66f343050ce709e904a");
115+
check_ph("0X9f057817ad576b24ec60a25ded08f5bde6db0aa0beeb0c099e3ce176866e1c4b");
116+
}
117+
118+
#[test]
119+
fn test_puzzle_hashes() {
120+
check_ph(&hex::encode([0; 32]));
121+
check_ph(&hex::encode([255; 32]));
122+
check_ph(&hex::encode([127; 32]));
123+
check_ph(&hex::encode([1; 32]));
124+
check_ph("f46ec440aeb9b3baa19968810a8537ec4ff406c09c994dd7d3222b87258a52ff");
125+
check_ph("2f981b2f9510ef9e62523e6b38fc933e2f060c411cfa64906413ddfd56be8dc1");
126+
check_ph("3e09bdd6b19659555a7c8456c5af54d004d774f3d44689360d4778ce685201ad");
127+
check_ph("d16c2ad7c5642532659e424dc0d7e4a85779c6dab801b5e6117a8c8587156472");
128+
}
129+
130+
#[test]
131+
fn test_invalid_puzzle_hashes() {
132+
assert_eq!(
133+
decode_puzzle_hash("ac4fd55996a1186fffc30c5b60385a88fd78d538f1c9febbfa9c8a9e9a170ad"),
134+
Err(PuzzleHashError::Decode(FromHexError::OddLength))
135+
);
136+
assert_eq!(
137+
decode_puzzle_hash(&hex::encode(hex!(
138+
"
139+
dfe399911acc4426f44bf31f4d817f6b69f244bbad138a28
140+
25c05550f7d2ab70c35408f764281febd624ac8cdfc91817
141+
"
142+
))),
143+
Err(PuzzleHashError::WrongLength(48))
144+
);
145+
assert_eq!(
146+
decode_puzzle_hash("hello there!"),
147+
Err(PuzzleHashError::Decode(FromHexError::InvalidHexCharacter {
148+
c: 'h',
149+
index: 0
150+
}))
151+
);
152+
}
153+
154+
#[test]
155+
fn test_addresses() {
156+
check_addr("xch1a0t57qn6uhe7tzjlxlhwy2qgmuxvvft8gnfzmg5detg0q9f3yc3s2apz0h");
157+
check_addr("xch1ftxk2v033kv94ueucp0a34sgt9398vle7l7g3q9k4leedjmmdysqvv6q96");
158+
check_addr("xch1ay273ctc9c6nxmzmzsup28scrce8ney84j4nlewdlaxqs22v53ksxgf38f");
159+
check_addr("xch1avnwmy2fuesq7h2jnxehlrs9msrad9uuvrhms35k2pqwmjv56y5qk7zm6v");
160+
}
161+
162+
#[test]
163+
fn test_invalid_addresses() {
164+
assert_eq!(
165+
decode_address("hello there!"),
166+
Err(AddressError::Decode(bech32::Error::MissingSeparator))
167+
);
168+
assert_eq!(
169+
decode_address("bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"),
170+
Err(AddressError::InvalidFormat)
171+
);
172+
}
173+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod condition;
77
mod signer;
88
mod spends;
99
mod ssl;
10+
mod stores;
1011
mod utils;
1112
mod wallet;
1213

@@ -15,6 +16,7 @@ pub use condition::*;
1516
pub use signer::*;
1617
pub use spends::*;
1718
pub use ssl::*;
19+
pub use stores::*;
1820
pub use wallet::*;
1921

2022
#[cfg(test)]

src/signer.rs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
use std::future::Future;
2-
3-
use chia_bls::{sign, PublicKey, SecretKey, Signature};
1+
use chia_bls::{sign, Signature};
42

53
use chia_protocol::{CoinSpend, SpendBundle};
64
use clvm_traits::{FromClvm, FromClvmError};
@@ -11,7 +9,7 @@ mod required_signature;
119

1210
pub use required_signature::*;
1311

14-
use crate::Condition;
12+
use crate::{Condition, SecretKeyStore};
1513

1614
/// An error that occurs while trying to sign a coin spend.
1715
#[derive(Debug, Error)]
@@ -29,15 +27,9 @@ pub enum SignSpendError {
2927
MissingKey,
3028
}
3129

32-
/// Responsible for signing messages.
33-
pub trait Signer {
34-
/// Gets the secret key for a given public key.
35-
fn secret_key(&self, public_key: &PublicKey) -> impl Future<Output = Option<SecretKey>> + Send;
36-
}
37-
3830
/// Signs each of the required messages in a coin spend.
3931
pub async fn sign_coin_spend(
40-
signer: &impl Signer,
32+
sk_store: &impl SecretKeyStore,
4133
allocator: &mut Allocator,
4234
coin_spend: &CoinSpend,
4335
agg_sig_me_extra_data: [u8; 32],
@@ -60,7 +52,7 @@ pub async fn sign_coin_spend(
6052
continue;
6153
};
6254

63-
let Some(sk) = signer.secret_key(required.public_key()).await else {
55+
let Some(sk) = sk_store.to_secret_key(required.public_key()).await else {
6456
return Err(SignSpendError::MissingKey);
6557
};
6658

@@ -72,15 +64,15 @@ pub async fn sign_coin_spend(
7264

7365
/// Signs each of the coin spends in a spend bundle.
7466
pub async fn sign_spend_bundle(
75-
signer: &impl Signer,
67+
sk_store: &impl SecretKeyStore,
7668
allocator: &mut Allocator,
7769
spend_bundle: &SpendBundle,
7870
agg_sig_me_extra_data: [u8; 32],
7971
) -> Result<Signature, SignSpendError> {
8072
let mut aggregate_signature = Signature::default();
8173
for coin_spend in &spend_bundle.coin_spends {
8274
let signature =
83-
sign_coin_spend(signer, allocator, coin_spend, agg_sig_me_extra_data).await?;
75+
sign_coin_spend(sk_store, allocator, coin_spend, agg_sig_me_extra_data).await?;
8476
aggregate_signature += &signature;
8577
}
8678
Ok(aggregate_signature)

src/spends/cat.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ pub async fn spend_cat_coins(
153153
// Coin info.
154154
let puzzle_hash = &coin.puzzle_hash;
155155
let index = derivation_store
156-
.index_of_puzzle_hash(puzzle_hash.into())
156+
.index_of_ph(puzzle_hash.into())
157157
.await
158158
.expect("cannot spend coin with unknown puzzle hash");
159159

src/spends/standard.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub async fn spend_standard_coins(
4646
for (i, coin) in coins.into_iter().enumerate() {
4747
let puzzle_hash = &coin.puzzle_hash;
4848
let index = derivation_store
49-
.index_of_puzzle_hash(puzzle_hash.into())
49+
.index_of_ph(puzzle_hash.into())
5050
.await
5151
.expect("cannot spend coin with unknown puzzle hash");
5252

src/stores.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
mod coin_store;
2+
mod derivation_store;
3+
mod public_key_store;
4+
mod secret_key_store;
5+
mod transaction_store;
6+
7+
pub use coin_store::*;
8+
pub use derivation_store::*;
9+
pub use public_key_store::*;
10+
pub use secret_key_store::*;
11+
pub use transaction_store::*;
File renamed without changes.

0 commit comments

Comments
 (0)