sui_crypto/
suipriv.rs

1//! Encoding and decoding helpers for the Sui `suiprivkey` Bech32 format.
2//!
3//! The format mirrors the one produced by the Sui CLI and the `sui-types`
4//! crate: a 33-byte payload of `flag || private_key` encoded as a Bech32
5//! (BIP-173) string with the human-readable part `suiprivkey`. The leading
6//! flag byte is the `SignatureScheme` flag for the contained key (`0x00` for
7//! Ed25519, `0x01` for Secp256k1, `0x02` for Secp256r1).
8//!
9//! These helpers are kept `pub(crate)` on purpose. Callers reach the format
10//! through strongly-typed wrappers like `Ed25519PrivateKey::from_suiprivkey`
11//! or `SimpleKeypair::from_suiprivkey` to avoid handing raw key bytes back as
12//! a `Vec<u8>` at the public boundary.
13
14use bech32::Bech32;
15use bech32::Hrp;
16use bech32::primitives::decode::CheckedHrpstring;
17use sui_sdk_types::SignatureScheme;
18
19use crate::SignatureError;
20
21/// The human-readable part of the `suiprivkey` Bech32 encoding.
22const HRP: &str = "suiprivkey";
23
24fn hrp() -> Hrp {
25    // "suiprivkey" is a valid Bech32 HRP (lowercase ASCII, length 10).
26    Hrp::parse(HRP).expect("`suiprivkey` is a valid Bech32 HRP")
27}
28
29/// Encode a `flag || private_key` payload as a Bech32 `suiprivkey` string.
30pub(crate) fn encode(scheme: SignatureScheme, key: &[u8]) -> Result<String, SignatureError> {
31    let mut payload = Vec::with_capacity(1 + key.len());
32    payload.push(scheme.to_u8());
33    payload.extend_from_slice(key);
34    bech32::encode::<Bech32>(hrp(), &payload)
35        .map_err(|e| SignatureError::from_source(format!("bech32 encode failed: {e}")))
36}
37
38/// Decode a Bech32 `suiprivkey` string into its scheme flag and key bytes.
39///
40/// The BIP-173 checksum is validated strictly; Bech32m-checksummed strings are
41/// rejected. The returned key bytes are everything after the flag byte and
42/// are not validated against the scheme — the caller is responsible for
43/// verifying the length and constructing a scheme-specific key from them.
44pub(crate) fn decode(s: &str) -> Result<(SignatureScheme, Vec<u8>), SignatureError> {
45    let parsed = CheckedHrpstring::new::<Bech32>(s)
46        .map_err(|e| SignatureError::from_source(format!("invalid suiprivkey string: {e}")))?;
47
48    if parsed.hrp() != hrp() {
49        return Err(SignatureError::from_source(format!(
50            "expected `{HRP}` human-readable part",
51        )));
52    }
53
54    let bytes: Vec<u8> = parsed.byte_iter().collect();
55    let (flag, key) = bytes
56        .split_first()
57        .ok_or_else(|| SignatureError::from_source("suipriv payload is empty"))?;
58    let scheme = SignatureScheme::from_byte(*flag)
59        .map_err(|e| SignatureError::from_source(format!("invalid suipriv scheme flag: {e}")))?;
60    Ok((scheme, key.to_vec()))
61}