hd_wallet/stark.rs
1//! Stark HD derivation
2//!
3//! This module provides [`Stark`] derivation as well as aliases for calling
4//! `<Stark as HdWallet<_>>::*` methods for convenience when you don't need to support
5//! generic HD derivation algorithm.
6//!
7//! See [`Stark`] docs to learn more about the derivation method.
8
9use generic_ec::{curves, Point, Scalar};
10use hmac::Mac;
11
12use crate::{
13 DeriveShift, DerivedShift, ExtendedKeyPair, ExtendedPublicKey, HardenedIndex, NonHardenedIndex,
14};
15
16type HmacSha512 = hmac::Hmac<sha2::Sha512>;
17
18/// HD derivation for stark curve
19///
20/// ## Algorithm
21/// The algorithm is a modification of BIP32:
22///
23/// ```text
24/// def derive_child_key(parent_public_key[, parent_secret_key], parent_chain_code, child_index):
25/// if is_hardened(child_index):
26/// i = HMAC_SHA512(key = parent_chain_code, 0x00 || 0x00 || parent_secret_key || child_index)
27/// || HMAC_SHA512(key = parent_chain_code, 0x01 || 0x00 || parent_secret_key || child_index)
28/// else:
29/// i = HMAC_SHA512(key = parent_chain_code, 0x00 || parent_public_key || child_index)
30/// || HMAC_SHA512(key = parent_chain_code, 0x01 || parent_public_key || child_index)
31/// shift = i[..96] mod order
32/// child_secret_key = parent_secret_key + shift and/or child_public_key = parent_public_key + shift G
33/// child_chain_code = i[N..]
34/// return child_public_key[, child_secret_key], child_chain_code
35/// ```
36///
37/// ## Other known methods for stark HD derivation
38/// There's another known method for HD derivation on stark curve implemented in
39/// [argent-x], which basically derives secp256k1 child key from a seed, and then
40/// uses grinding function to deterministically convert it into stark key.
41///
42/// We decided not to implement it due to its cons:
43/// * No support for non-hardened derivation
44/// * Grinding is a probabilistic algorithm which does a lot of hashing (32 hashes
45/// on average, but in worst case can be 1000+).
46/// * In general, it's strange to derive secp256k1 key and then convert it to stark key
47///
48/// Our derivation algorithm addresses these flaws: it yields a stark key right away (without
49/// any intermediate secp256k1 keys), supports non-hardened derivation, does only 2 hashes per
50/// derivation.
51///
52/// [argent-x]: https://github.com/argentlabs/argent-x/blob/13142607d83fea10b297d6a23452e810605784d1/packages/extension/src/shared/signer/ArgentSigner.ts#L14-L25,
53pub struct Stark;
54
55impl DeriveShift<curves::Stark> for Stark {
56 fn derive_public_shift(
57 parent_public_key: &ExtendedPublicKey<curves::Stark>,
58 child_index: NonHardenedIndex,
59 ) -> DerivedShift<curves::Stark> {
60 let hmac = HmacSha512::new_from_slice(&parent_public_key.chain_code)
61 .expect("this never fails: hmac can handle keys of any size");
62 let i0 = hmac
63 .clone()
64 .chain_update([0x00])
65 .chain_update(parent_public_key.public_key.to_bytes(true))
66 .chain_update(child_index.to_be_bytes())
67 .finalize()
68 .into_bytes();
69 let i1 = hmac
70 .chain_update([0x01])
71 .chain_update(parent_public_key.public_key.to_bytes(true))
72 .chain_update(child_index.to_be_bytes())
73 .finalize()
74 .into_bytes();
75 Self::calculate_shift(parent_public_key, i0, i1)
76 }
77
78 fn derive_hardened_shift(
79 parent_key: &ExtendedKeyPair<curves::Stark>,
80 child_index: HardenedIndex,
81 ) -> DerivedShift<curves::Stark> {
82 let hmac = HmacSha512::new_from_slice(parent_key.chain_code())
83 .expect("this never fails: hmac can handle keys of any size");
84 let i0 = hmac
85 .clone()
86 .chain_update([0x00])
87 .chain_update([0x00])
88 .chain_update(parent_key.secret_key.secret_key.as_ref().to_be_bytes())
89 .chain_update(child_index.to_be_bytes())
90 .finalize()
91 .into_bytes();
92 let i1 = hmac
93 .chain_update([0x01])
94 .chain_update([0x00])
95 .chain_update(parent_key.secret_key.secret_key.as_ref().to_be_bytes())
96 .chain_update(child_index.to_be_bytes())
97 .finalize()
98 .into_bytes();
99 Self::calculate_shift(&parent_key.public_key, i0, i1)
100 }
101}
102
103impl Stark {
104 fn calculate_shift(
105 parent_public_key: &ExtendedPublicKey<curves::Stark>,
106 i0: hmac::digest::Output<HmacSha512>,
107 i1: hmac::digest::Output<HmacSha512>,
108 ) -> DerivedShift<curves::Stark> {
109 let i = generic_array::sequence::Concat::concat(i0, i1);
110 let (shift, chain_code) = split(&i);
111
112 let shift = Scalar::from_be_bytes_mod_order(shift);
113 let child_pk = parent_public_key.public_key + Point::generator() * shift;
114
115 DerivedShift {
116 shift,
117 child_public_key: ExtendedPublicKey {
118 public_key: child_pk,
119 chain_code: (*chain_code).into(),
120 },
121 }
122 }
123}
124
125fn split(
126 i: &generic_array::GenericArray<u8, generic_array::typenum::U128>,
127) -> (
128 &generic_array::GenericArray<u8, generic_array::typenum::U96>,
129 &generic_array::GenericArray<u8, generic_array::typenum::U32>,
130) {
131 generic_array::sequence::Split::split(i)
132}
133
134super::create_aliases!(Stark, stark, hd_wallet::curves::Stark);