docs: better info about writing types containing data

This commit is contained in:
Ryan Heywood 2024-01-18 23:50:23 -05:00
parent bf50e2aeac
commit 2e3c387ae1
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
6 changed files with 139 additions and 10 deletions

View File

@ -78,8 +78,8 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
#[cfg(feature = "qrcode")]
{
use keyfork_qrcode::{qrencode, ErrorCorrection};
let mut qrcode_data = nonce_mnemonic.entropy();
qrcode_data.extend(key_mnemonic.entropy());
let mut qrcode_data = nonce_mnemonic.to_bytes();
qrcode_data.extend(key_mnemonic.as_bytes());
if let Ok(qrcode) = qrencode(&smex::encode(&qrcode_data), ErrorCorrection::Medium) {
pm.prompt_message(PromptMessage::Data(qrcode))?;
}
@ -120,10 +120,10 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
let [pubkey_mnemonic, payload_mnemonic] =
pm.prompt_validated_wordlist("Their words: ", &wordlist, 3, validator.to_fn())?;
let pubkey = pubkey_mnemonic
.entropy()
.as_bytes()
.try_into()
.map_err(|_| InvalidData)?;
let payload = payload_mnemonic.entropy();
let payload = payload_mnemonic.to_bytes();
(pubkey, payload)
}
};

View File

@ -495,11 +495,11 @@ pub fn decrypt(
pm.prompt_validated_wordlist("Their words: ", &wordlist, 3, validator.to_fn())?;
let nonce = nonce_mnemonic
.entropy()
.as_bytes()
.try_into()
.map_err(|_| InvalidData)?;
let pubkey = pubkey_mnemonic
.entropy()
.as_bytes()
.try_into()
.map_err(|_| InvalidData)?;
(nonce, pubkey)
@ -560,8 +560,8 @@ pub fn decrypt(
#[cfg(feature = "qrcode")]
{
use keyfork_qrcode::{qrencode, ErrorCorrection};
let mut qrcode_data = our_pubkey_mnemonic.entropy();
qrcode_data.extend(payload_mnemonic.entropy());
let mut qrcode_data = our_pubkey_mnemonic.to_bytes();
qrcode_data.extend(payload_mnemonic.as_bytes());
if let Ok(qrcode) = qrencode(&smex::encode(&qrcode_data), ErrorCorrection::Lowest) {
pm.prompt_message(PromptMessage::Data(qrcode))?;
}

View File

@ -75,7 +75,7 @@ impl RecoverSubcommands {
3,
validator.to_fn(),
)?;
Ok(mnemonic.entropy())
Ok(mnemonic.to_bytes())
}
}
}

View File

@ -265,17 +265,27 @@ impl Mnemonic {
&self.entropy
}
/// The internal representation of the decoded data, as a [`Vec<u8>`].
pub fn to_bytes(&self) -> Vec<u8> {
self.entropy.to_vec()
}
/// Drop self, returning the decoded data.
pub fn to_bytes(self) -> Vec<u8> {
pub fn into_bytes(self) -> Vec<u8> {
self.entropy
}
/// Clone the existing entropy.
#[deprecated]
pub fn entropy(&self) -> Vec<u8> {
self.entropy.clone()
}
/// Create a BIP-0032 seed from the provided data and an optional passphrase.
///
/// # Errors
/// The method may return an error if the pbkdf2 function returns an invalid length, but this
/// case should not be reached.
pub fn seed<'a>(
&self,
passphrase: impl Into<Option<&'a str>>,

View File

@ -25,6 +25,7 @@
# Developers Guide
- [Handling Data](./dev-guide/handling-data.md)
- [Writing Binaries](./dev-guide/index.md)
- [Provisioners](./dev-guide/provisioners.md)
- [Auditing Dependencies](./dev-guide/auditing.md)

View File

@ -0,0 +1,118 @@
# Handling Data
In Rust, it is common to name things `as_*`, `to_*`, and `into_*`. These three
methods perform three different, but related, functions, and it's important to
choose the correct name when writing methods. These methods take data that are
stored inside of the struct, and return them in some manner. For example, let's
look at the `Mnemonic` struct:
```rust
pub struct Mnemonic {
entropy: Vec<u8>,
// fields omitted
}
```
We're going to define the three methods `as_bytes()`, `to_bytes()`, and
`into_bytes()`, explaining what the details of each are. To start with:
### `Mnemonic::as_bytes()`
This method will return a `&[u8]`, a reference to a slice of bytes, without
allocating anything. The `Mnemonic` type is not consumed by this process.
```rust
impl Mnemonic {
/// Return a slice of bytes represented by the mnemonic.
pub fn as_bytes(&self) -> &[u8] {
&self.entropy
}
}
```
### `Mnemonic::to_bytes()`
This method will return a `Vec<u8>`, an allocated type containing the bytes,
without consuming the `Mnemonic` type. This is useful if you may need the
`Mnemonic` type in the future.
```rust
impl Mnemonic {
/// Return a `Vec<u8>` of bytes represented by the mnemonic.
pub fn to_bytes(&self) -> Vec<u8> {
self.entropy.to_vec()
}
}
```
### `Mnemonic::into_bytes()`
This method will return the internal representation of bytes. No allocation is
performed, but the `Mnemonic` value is no longer accessible.
```rust
impl Mnemonic {
/// Return a Vec<u8> of bytes represented by the mnemonic.
pub fn into_bytes(self) -> Vec<u8> {
self.entropy
}
}
```
## Creating Data: `Mnemonic::seed()`
Sometimes you may have an operation (such as creating a seed from a mnemonic)
which can't possibly happen without allocating some kind of data. In that case,
it is not reasonable - or even possible - to have three methods for that case.
In that case, it is reasonable to have a method with a "bare" name, such as
`Mnemonic::seed()`. Additionally, unless it makes sense to make the type
unusable after such an operation (using the type system to your advantage), it
makes sense to borrow from self while returning the allocated value.
```rust
impl Mnemonic {
/// Create a seed from the mnemonic's data.
pub fn seed(&self) -> Result<Vec<u8>, Error> {
let result = perform_secret_operation(self.entropy)?;
Ok(result)
}
}
```
## Complex Example: `Mnemonic::into_parts()`
Let's add a `Wordlist` to our `Mnemonic` type, so we can format it as a string.
```rust
pub struct Mnemonic {
entropy: Vec<u8>,
wordlist: Wordlist,
}
```
Because we now have two expensive-to-allocate variables in our type, we need to
change how we deconstruct it into parts:
```rust
impl Mnemonic {
/// Split the Mnemonic into its interior parts.
pub fn into_parts(self) -> (Vec<u8>, Wordlist) {
let Mnemonic { entropy, wordlist } = self;
(entropy, wordlist)
}
/// Return a Vec<u8> of bytes represented by the mnemonic.
pub fn into_bytes(self) -> Vec<u8> {
self.into_parts().0
}
/// Return the Wordlist used by the mnemonic.
pub fn into_wordlist(self) -> Wordlist {
self.into_parts().1
}
}
```
In practical use, `Mnemonic` stores `wordlist` as an `Arc<T>` to allow reusing
the same wordlist (parsing it is expensive!), but we'll ignore that for
demonstration purposes.