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//! ## Security and Compatibility
19//!
20//! This derivation path scheme is **critical** for compatibility with other Spark wallets.
21//! All Spark wallets must follow this exact scheme to ensure interoperability and proper
22//! handling of funds within the Spark ecosystem.
23//!
24//! # WARNING
25//!
26//! Implementing this trait with non-standard derivation paths will make your wallet
27//! incompatible with the Spark ecosystem. Only implement this trait if you fully understand
28//! the consequences and are intentionally creating a non-standard wallet.
29
30use crate::error::SparkSdkError;
31
32use bitcoin::{
33    bip32::ChildNumber,
34    secp256k1::{PublicKey, SecretKey},
35    Network,
36};
37
38/// Represents a BIP32 derivation path for wallet keys.
39///
40/// This struct wraps a vector of `ChildNumber` values that define a complete
41/// derivation path for HD wallet keys. It provides convenience methods for
42/// formatting and accessing the path components.
43///
44/// # Examples
45///
46/// ```
47/// # use spark_rust::signer::traits::derivation_path::SparkDerivationPath;
48/// # use bitcoin::bip32::ChildNumber;
49/// # fn example() {
50/// // Create a path with purpose, account, key type (identity)
51/// let purpose = ChildNumber::from_hardened_idx(8797555).unwrap();
52/// let account = ChildNumber::from_hardened_idx(0).unwrap();
53/// let key_type = ChildNumber::from_hardened_idx(0).unwrap();
54///
55/// let path = SparkDerivationPath(vec![purpose, account, key_type]);
56///
57/// // Format as string: "m/8797555'/0'/0'"
58/// let path_str = path.to_string();
59/// # }
60/// ```
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct SparkDerivationPath(pub(crate) Vec<ChildNumber>);
63
64impl std::ops::Deref for SparkDerivationPath {
65    type Target = Vec<ChildNumber>;
66
67    fn deref(&self) -> &Self::Target {
68        &self.0
69    }
70}
71
72impl std::fmt::Display for SparkDerivationPath {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        let mut output = String::new();
75
76        // Only add the "m/" prefix for paths with more than one element
77        // For single-element paths (like leaf indices), don't add the prefix
78        if self.0.len() > 1 {
79            output.push('m');
80        }
81
82        // Format each child number in the path
83        for (i, child_number) in self.0.iter().enumerate() {
84            // Only add slash if it's not the first element or if we have the m/ prefix
85            if i > 0 || self.0.len() > 1 {
86                output.push('/');
87            }
88
89            // Get the index value
90            let index = match child_number {
91                ChildNumber::Hardened { index } => index,
92                ChildNumber::Normal { index } => index,
93            };
94
95            // Write the index
96            output.push_str(&index.to_string());
97
98            // Add apostrophe for hardened indices
99            if child_number.is_hardened() {
100                output.push('\'');
101            }
102        }
103
104        write!(f, "{}", output)
105    }
106}
107
108/// Key types for Spark wallet.
109///
110/// Each key type corresponds to a specific path in the hierarchical deterministic
111/// wallet structure, with the path format: m/8797555'/account'/key_type'
112///
113/// The key types serve different purposes in the Spark wallet:
114/// - Identity key: Used for wallet authentication and identification
115/// - Base signing key: Used as the foundation for leaf-specific keys
116/// - Temporary signing key: Used for one-time operations like deposits
117///
118/// These key types are encoded as the third component in the derivation path.
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum SparkKeyType {
121    /// Identity key with path m/8797555'/account'/0'
122    ///
123    /// This key is used for wallet authentication, identification,
124    /// and signature verification with Spark Operators.
125    Identity,
126
127    /// Base signing key with path m/8797555'/account'/1'
128    ///
129    /// This key serves as the foundation for leaf-specific keys,
130    /// which are derived by adding a leaf-specific index.
131    BaseSigning,
132
133    /// Temporary signing key with path m/8797555'/account'/2'
134    ///
135    /// This key is used for one-time operations such as
136    /// deposit address generation and temporary signing operations.
137    TemporarySigning,
138}
139
140/// Trait for key derivation operations in the Spark wallet.
141///
142/// This trait defines the methods required to derive keys according to the
143/// Spark-specific derivation path scheme. Implementors must ensure they
144/// follow the exact derivation path structure to maintain compatibility
145/// with the Spark ecosystem.
146///
147/// The Spark derivation scheme follows:
148/// - m/8797555'/account'/key_type' for main keys
149/// - m/8797555'/account'/1'/hash(leaf_id)' for leaf-specific keys
150///
151/// Where:
152/// - 8797555' is the purpose value (derived from "spark")
153/// - account' is the account index (starting from 0)
154/// - key_type' is the key type (0' for identity, 1' for base signing, 2' for temporary)
155/// - hash(leaf_id)' is a hardened index derived from the leaf ID
156pub trait SparkSignerDerivationPath {
157    /// Derives the deposit signing key for the user.
158    ///
159    /// In Spark, users always have a single deposit key derived deterministically
160    /// from their seed. This method returns the public key corresponding to the
161    /// derived deposit signing key.
162    ///
163    /// The deposit signing key uses the `TemporarySigning` key type (path index 2')
164    /// in the derivation path.
165    ///
166    /// # Arguments
167    /// * `network` - The Bitcoin network to use for the key derivation
168    ///
169    /// # Returns
170    /// The public key for deposit operations or an error if derivation fails
171    fn get_deposit_signing_key(&self, network: Network) -> Result<PublicKey, SparkSdkError>;
172
173    /// Derives a Spark key for the specified key type, account, and optionally leaf ID.
174    ///
175    /// This is a core method for the Spark derivation path system. It handles the
176    /// derivation of all types of keys used in the Spark wallet, including identity
177    /// keys, base signing keys, temporary signing keys, and leaf-specific keys.
178    ///
179    /// # Arguments
180    /// * `leaf_id` - Optional leaf ID for leaf-specific keys. If provided, the key
181    ///   will be derived using the leaf-specific path: m/8797555'/account'/1'/hash(leaf_id)'
182    /// * `account` - Account index to use in the derivation path
183    /// * `seed_bytes` - The seed bytes to derive the key from
184    /// * `key_type` - The type of key to derive (Identity, BaseSigning, or TemporarySigning)
185    /// * `network` - The Bitcoin network to use for the key derivation
186    ///
187    /// # Returns
188    /// The derived secret key or an error if derivation fails
189    fn derive_spark_key(
190        leaf_id: Option<String>,
191        account: u32,
192        seed_bytes: &[u8],
193        key_type: SparkKeyType,
194        network: Network,
195    ) -> Result<SecretKey, SparkSdkError>;
196
197    /// Returns the derivation path for the identity key.
198    ///
199    /// The identity key has the path: m/8797555'/account'/0'
200    /// This method constructs and returns this path for the specified account.
201    ///
202    /// # Arguments
203    /// * `account_index` - The account index to use in the derivation path
204    ///
205    /// # Returns
206    /// A `SparkDerivationPath` containing the identity key path components
207    /// or an error if path construction fails
208    fn get_identity_derivation_path(
209        account_index: u32,
210    ) -> Result<SparkDerivationPath, SparkSdkError>;
211}