-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Denis Varlakov <[email protected]>
- Loading branch information
Showing
8 changed files
with
466 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
use generic_ec::{Curve, Point, SecretScalar}; | ||
use rand::Rng; | ||
|
||
fn main() { | ||
generate_test_vectors::<generic_ec::curves::Stark, hd_wallet::Stark>(); | ||
} | ||
|
||
fn generate_test_vectors<E: Curve, Hd: hd_wallet::HdWallet<E>>() { | ||
let mut rng = rand::thread_rng(); | ||
|
||
let sk = SecretScalar::<E>::random(&mut rng); | ||
let pk = Point::generator() * &sk; | ||
let chain_code: hd_wallet::ChainCode = rng.gen(); | ||
|
||
println!("sk: {}", hex::encode(sk.as_ref().to_be_bytes())); | ||
println!("pk: {}", hex::encode(pk.to_bytes(true))); | ||
println!("chain code: {}", hex::encode(chain_code)); | ||
|
||
let paths: &[&[u32]] = &[ | ||
// Non-hardened derivation | ||
&[0], | ||
&[1], | ||
&[2], | ||
&[ | ||
rng.gen_range(0..hd_wallet::H), | ||
rng.gen_range(0..hd_wallet::H), | ||
rng.gen_range(0..hd_wallet::H), | ||
rng.gen_range(0..hd_wallet::H), | ||
], | ||
&[ | ||
rng.gen_range(0..hd_wallet::H), | ||
rng.gen_range(0..hd_wallet::H), | ||
rng.gen_range(0..hd_wallet::H), | ||
rng.gen_range(0..hd_wallet::H), | ||
], | ||
// Hardened derivation | ||
&[0 + hd_wallet::H], | ||
&[1 + hd_wallet::H], | ||
&[2 + hd_wallet::H], | ||
// Mixed hardened and non-hardened derivation | ||
&[ | ||
rng.gen_range(0..hd_wallet::H), | ||
rng.gen_range(hd_wallet::H..=u32::MAX), | ||
rng.gen_range(0..hd_wallet::H), | ||
rng.gen_range(hd_wallet::H..=u32::MAX), | ||
], | ||
&[ | ||
rng.gen_range(0..hd_wallet::H), | ||
rng.gen_range(0..hd_wallet::H), | ||
rng.gen_range(hd_wallet::H..=u32::MAX), | ||
rng.gen_range(hd_wallet::H..=u32::MAX), | ||
], | ||
]; | ||
|
||
let key = hd_wallet::ExtendedSecretKey { | ||
secret_key: sk, | ||
chain_code, | ||
}; | ||
let key = hd_wallet::ExtendedKeyPair::from(key); | ||
assert_eq!(key.public_key().public_key, pk); | ||
|
||
for path in paths { | ||
println!("{:?}", PathFmt(path)); | ||
let child_key = Hd::derive_child_key_pair_with_path(&key, path.iter().copied()); | ||
println!( | ||
"child secret key: {}", | ||
hex::encode(child_key.secret_key().secret_key.as_ref().to_be_bytes()) | ||
); | ||
println!( | ||
"child public key: {}", | ||
hex::encode(child_key.public_key().public_key.to_bytes(true)) | ||
); | ||
println!("child chain code: {}", hex::encode(child_key.chain_code())); | ||
println!(); | ||
} | ||
} | ||
|
||
struct PathFmt<'a>(&'a [u32]); | ||
|
||
impl<'a> std::fmt::Debug for PathFmt<'a> { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "[")?; | ||
for (is_first, index) in std::iter::once(true) | ||
.chain(std::iter::repeat(false)) | ||
.zip(self.0) | ||
{ | ||
if !is_first { | ||
write!(f, ", ")?; | ||
} | ||
|
||
if *index < hd_wallet::H { | ||
write!(f, "{index}")?; | ||
} else { | ||
write!( | ||
f, | ||
"{} + hd_wallet::H", | ||
index.checked_sub(hd_wallet::H).unwrap() | ||
)?; | ||
} | ||
} | ||
write!(f, "]") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
//! Stark HD derivation | ||
//! | ||
//! This module provides [`Stark`] derivation as well as aliases for calling | ||
//! `<Stark as HdWallet<_>>::*` methods for convenience when you don't need to support | ||
//! generic HD derivation algorithm. | ||
//! | ||
//! See [`Stark`] docs to learn more about the derivation method. | ||
use generic_ec::{curves, Point, Scalar}; | ||
use hmac::Mac; | ||
|
||
use crate::{ | ||
DeriveShift, DerivedShift, ExtendedKeyPair, ExtendedPublicKey, HardenedIndex, NonHardenedIndex, | ||
}; | ||
|
||
type HmacSha512 = hmac::Hmac<sha2::Sha512>; | ||
|
||
/// HD derivation for stark curve | ||
/// | ||
/// ## Algorithm | ||
/// The algorithm is a modification of BIP32: | ||
/// | ||
/// ```text | ||
/// def derive_child_key(parent_public_key[, parent_secret_key], parent_chain_code, child_index): | ||
/// if is_hardened(child_index): | ||
/// i = HMAC_SHA512(key = parent_chain_code, 0x00 || 0x00 || parent_secret_key || child_index) | ||
/// || HMAC_SHA512(key = parent_chain_code, 0x01 || 0x00 || parent_secret_key || child_index) | ||
/// else: | ||
/// i = HMAC_SHA512(key = parent_chain_code, 0x00 || parent_public_key || child_index) | ||
/// || HMAC_SHA512(key = parent_chain_code, 0x01 || parent_public_key || child_index) | ||
/// shift = i[..96] mod order | ||
/// child_secret_key = parent_secret_key + shift and/or child_public_key = parent_public_key + shift G | ||
/// child_chain_code = i[N..] | ||
/// return child_public_key[, child_secret_key], child_chain_code | ||
/// ``` | ||
/// | ||
/// ## Other known methods for stark HD derivation | ||
/// There's another known method for HD derivation on stark curve implemented in | ||
/// [argent-x], which basically derives secp256k1 child key from a seed, and then | ||
/// uses grinding function to deterministically convert it into stark key. | ||
/// | ||
/// We decided not to implement it due to its cons: | ||
/// * No support for non-hardened derivation | ||
/// * Grinding is a probabilistic algorithm which does a lot of hashing (32 hashes | ||
/// on average, but in worst case can be 1000+). | ||
/// * In general, it's strange to derive secp256k1 key and then convert it to stark key | ||
/// | ||
/// Our derivation algorithm addresses these flaws: it yields a stark key right away (without | ||
/// any intermediate secp256k1 keys), supports non-hardened derivation, does only 2 hashes per | ||
/// derivation. | ||
/// | ||
/// [argent-x]: https://github.com/argentlabs/argent-x/blob/13142607d83fea10b297d6a23452e810605784d1/packages/extension/src/shared/signer/ArgentSigner.ts#L14-L25, | ||
pub struct Stark; | ||
|
||
impl DeriveShift<curves::Stark> for Stark { | ||
fn derive_public_shift( | ||
parent_public_key: &ExtendedPublicKey<curves::Stark>, | ||
child_index: NonHardenedIndex, | ||
) -> DerivedShift<curves::Stark> { | ||
let hmac = HmacSha512::new_from_slice(&parent_public_key.chain_code) | ||
.expect("this never fails: hmac can handle keys of any size"); | ||
let i0 = hmac | ||
.clone() | ||
.chain_update([0x00]) | ||
.chain_update(parent_public_key.public_key.to_bytes(true)) | ||
.chain_update(child_index.to_be_bytes()) | ||
.finalize() | ||
.into_bytes(); | ||
let i1 = hmac | ||
.chain_update([0x01]) | ||
.chain_update(parent_public_key.public_key.to_bytes(true)) | ||
.chain_update(child_index.to_be_bytes()) | ||
.finalize() | ||
.into_bytes(); | ||
Self::calculate_shift(parent_public_key, i0, i1) | ||
} | ||
|
||
fn derive_hardened_shift( | ||
parent_key: &ExtendedKeyPair<curves::Stark>, | ||
child_index: HardenedIndex, | ||
) -> DerivedShift<curves::Stark> { | ||
let hmac = HmacSha512::new_from_slice(parent_key.chain_code()) | ||
.expect("this never fails: hmac can handle keys of any size"); | ||
let i0 = hmac | ||
.clone() | ||
.chain_update([0x00]) | ||
.chain_update([0x00]) | ||
.chain_update(parent_key.secret_key.secret_key.as_ref().to_be_bytes()) | ||
.chain_update(child_index.to_be_bytes()) | ||
.finalize() | ||
.into_bytes(); | ||
let i1 = hmac | ||
.chain_update([0x01]) | ||
.chain_update([0x00]) | ||
.chain_update(parent_key.secret_key.secret_key.as_ref().to_be_bytes()) | ||
.chain_update(child_index.to_be_bytes()) | ||
.finalize() | ||
.into_bytes(); | ||
Self::calculate_shift(&parent_key.public_key, i0, i1) | ||
} | ||
} | ||
|
||
impl Stark { | ||
fn calculate_shift( | ||
parent_public_key: &ExtendedPublicKey<curves::Stark>, | ||
i0: hmac::digest::Output<HmacSha512>, | ||
i1: hmac::digest::Output<HmacSha512>, | ||
) -> DerivedShift<curves::Stark> { | ||
let i = generic_array::sequence::Concat::concat(i0, i1); | ||
let (shift, chain_code) = split(&i); | ||
|
||
let shift = Scalar::from_be_bytes_mod_order(shift); | ||
let child_pk = parent_public_key.public_key + Point::generator() * shift; | ||
|
||
DerivedShift { | ||
shift, | ||
child_public_key: ExtendedPublicKey { | ||
public_key: child_pk, | ||
chain_code: (*chain_code).into(), | ||
}, | ||
} | ||
} | ||
} | ||
|
||
fn split( | ||
i: &generic_array::GenericArray<u8, generic_array::typenum::U128>, | ||
) -> ( | ||
&generic_array::GenericArray<u8, generic_array::typenum::U96>, | ||
&generic_array::GenericArray<u8, generic_array::typenum::U32>, | ||
) { | ||
generic_array::sequence::Split::split(i) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.