spark_rust/signer/traits/derivation_path.rs
1//! Key derivation path handling for Spark wallet.
2//!
3//! This module implements the key derivation scheme for the Spark wallet, following
4//! BIP43 conventions with a custom purpose identifier. The scheme is structured as:
5//!
6//! - Identity key: m/8797555'/account'/0'
7//! - Base signing key: m/8797555'/account'/1'
8//! - Temporary signing key: m/8797555'/account'/2'
9//!
10//! For leaf keys, an additional index is derived from the leaf ID:
11//! - Leaf key: m/8797555'/account'/1'/hash(leaf_id)'
12//!
13//! The purpose value 8797555 is derived as the last 3 bytes of sha256("spark") in decimal (863d73).
14//!
15//! All indices use hardened derivation (denoted by the apostrophe) for enhanced security.
16//! Account indices should start from 0.
17//!
18//! # WARNING
19//!
20//! Implementing this trait with non-standard derivation paths will make your wallet
21//! incompatible with the Spark ecosystem. Only implement this trait if you fully understand
22//! the consequences and are intentionally creating a non-standard wallet.
23
24use crate::error::SparkSdkError;
25
26use bitcoin::{
27 bip32::ChildNumber,
28 secp256k1::{PublicKey, SecretKey},
29 Network,
30};
31
32/// Represents a BIP32 derivation path for wallet keys.
33///
34/// This struct wraps a vector of `ChildNumber` values that define a complete
35/// derivation path for HD wallet keys. It provides convenience methods for
36/// formatting and accessing the path components.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct SparkDerivationPath(pub(crate) Vec<ChildNumber>);
39
40impl std::ops::Deref for SparkDerivationPath {
41 type Target = Vec<ChildNumber>;
42
43 fn deref(&self) -> &Self::Target {
44 &self.0
45 }
46}
47
48impl std::fmt::Display for SparkDerivationPath {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 let mut output = String::new();
51
52 // Only add the "m/" prefix for paths with more than one element
53 // For single-element paths (like leaf indices), don't add the prefix
54 if self.0.len() > 1 {
55 output.push('m');
56 }
57
58 // Format each child number in the path
59 for (i, child_number) in self.0.iter().enumerate() {
60 // Only add slash if it's not the first element or if we have the m/ prefix
61 if i > 0 || self.0.len() > 1 {
62 output.push('/');
63 }
64
65 // Get the index value
66 let index = match child_number {
67 ChildNumber::Hardened { index } => index,
68 ChildNumber::Normal { index } => index,
69 };
70
71 // Write the index
72 output.push_str(&index.to_string());
73
74 // Add apostrophe for hardened indices
75 if child_number.is_hardened() {
76 output.push('\'');
77 }
78 }
79
80 write!(f, "{}", output)
81 }
82}
83
84/// Key types for Spark wallet.
85///
86/// Each key type corresponds to a specific path in the hierarchical deterministic
87/// wallet structure, with the path format: m/8797555'/account'/key_type'
88///
89/// - Identity key (type 0): Used for wallet identity purposes
90/// - Base signing key (type 1): Used as the basis for leaf keys
91/// - Temporary signing key (type 2): Used for deposit/temporary operations
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum SparkKeyType {
94 /// Identity key with path m/8797555'/account'/0'
95 Identity,
96
97 /// Base signing key with path m/8797555'/account'/1'
98 BaseSigning,
99
100 /// Temporary signing key with path m/8797555'/account'/2'
101 TemporarySigning,
102}
103
104pub trait SparkSignerDerivationPath {
105 /// Derives the deposit signing key for the user. In Spark, currently users always have a single deposit key, derived deterministically from their seed.
106 fn get_deposit_signing_key(&self, network: Network) -> Result<PublicKey, SparkSdkError>;
107
108 /// Derives a Spark key for the specified key type, account, and optionally leaf ID. If the leaf ID is not provided, the function uses the identity.
109 fn derive_spark_key(
110 leaf_id: Option<String>,
111 account: u32,
112 seed_bytes: &[u8],
113 key_type: SparkKeyType,
114 network: Network,
115 ) -> Result<SecretKey, SparkSdkError>;
116
117 /// Returns the derivation path for the identity key.
118 fn get_identity_derivation_path(
119 account_index: u32,
120 ) -> Result<SparkDerivationPath, SparkSdkError>;
121}