@@ -33,13 +33,15 @@ use secp256k1::{self, XOnlyPublicKey};
3333use serde;
3434
3535use crate :: dip9:: {
36- ASSET_LOCK_ADDRESS_TOPUP_PATH_MAINNET , ASSET_LOCK_ADDRESS_TOPUP_PATH_TESTNET ,
37- ASSET_LOCK_SHIELDED_ADDRESS_TOPUP_PATH_MAINNET , ASSET_LOCK_SHIELDED_ADDRESS_TOPUP_PATH_TESTNET ,
38- COINJOIN_PATH_MAINNET , COINJOIN_PATH_TESTNET , DASH_BIP44_PATH_MAINNET , DASH_BIP44_PATH_TESTNET ,
39- IDENTITY_AUTHENTICATION_PATH_MAINNET , IDENTITY_AUTHENTICATION_PATH_TESTNET ,
40- IDENTITY_INVITATION_PATH_MAINNET , IDENTITY_INVITATION_PATH_TESTNET ,
41- IDENTITY_REGISTRATION_PATH_MAINNET , IDENTITY_REGISTRATION_PATH_TESTNET ,
42- IDENTITY_TOPUP_PATH_MAINNET , IDENTITY_TOPUP_PATH_TESTNET ,
36+ APPLICATION_ENCRYPTION_PATH_MAINNET , APPLICATION_ENCRYPTION_PATH_TESTNET ,
37+ APPLICATION_SESSION_AUTHENTICATION_PATH_MAINNET ,
38+ APPLICATION_SESSION_AUTHENTICATION_PATH_TESTNET , ASSET_LOCK_ADDRESS_TOPUP_PATH_MAINNET ,
39+ ASSET_LOCK_ADDRESS_TOPUP_PATH_TESTNET , ASSET_LOCK_SHIELDED_ADDRESS_TOPUP_PATH_MAINNET ,
40+ ASSET_LOCK_SHIELDED_ADDRESS_TOPUP_PATH_TESTNET , COINJOIN_PATH_MAINNET , COINJOIN_PATH_TESTNET ,
41+ DASH_BIP44_PATH_MAINNET , DASH_BIP44_PATH_TESTNET , IDENTITY_AUTHENTICATION_PATH_MAINNET ,
42+ IDENTITY_AUTHENTICATION_PATH_TESTNET , IDENTITY_INVITATION_PATH_MAINNET ,
43+ IDENTITY_INVITATION_PATH_TESTNET , IDENTITY_REGISTRATION_PATH_MAINNET ,
44+ IDENTITY_REGISTRATION_PATH_TESTNET , IDENTITY_TOPUP_PATH_MAINNET , IDENTITY_TOPUP_PATH_TESTNET ,
4345} ;
4446use base58ck;
4547#[ cfg( feature = "bincode" ) ]
@@ -1025,6 +1027,24 @@ impl From<KeyDerivationType> for u32 {
10251027 }
10261028}
10271029
1030+ /// The `key_purpose'` level of a DIP-13 application encryption path: the Platform identity key
1031+ /// purpose the derived key is registered with.
1032+ #[ derive( Copy , Clone , Debug , Eq , PartialEq , Ord , PartialOrd ) ]
1033+ #[ repr( u32 ) ]
1034+ pub enum ApplicationKeyPurpose {
1035+ Encryption = 1 ,
1036+ Decryption = 2 ,
1037+ }
1038+
1039+ impl From < ApplicationKeyPurpose > for u32 {
1040+ fn from ( val : ApplicationKeyPurpose ) -> Self {
1041+ match val {
1042+ ApplicationKeyPurpose :: Encryption => 1 ,
1043+ ApplicationKeyPurpose :: Decryption => 2 ,
1044+ }
1045+ }
1046+ }
1047+
10281048impl DerivationPath {
10291049 pub fn bip_44_account ( network : Network , account : u32 ) -> Self {
10301050 let mut root_derivation_path: DerivationPath = match network {
@@ -1176,6 +1196,66 @@ impl DerivationPath {
11761196 root_derivation_path
11771197 }
11781198
1199+ /// DIP-13 application session authentication key path,
1200+ /// `m/9'/coin_type'/5'/6'/0'/identity_id'/request_id'`. The identity id and request id are
1201+ /// DIP-14 256-bit hardened children, which only secp256k1 derivation defines, so the key type
1202+ /// level is always ECDSA (`0'`).
1203+ pub fn application_session_authentication_path (
1204+ network : Network ,
1205+ identity_id : [ u8 ; 32 ] ,
1206+ request_id : [ u8 ; 32 ] ,
1207+ ) -> Self {
1208+ let mut root_derivation_path: DerivationPath = match network {
1209+ Network :: Mainnet => APPLICATION_SESSION_AUTHENTICATION_PATH_MAINNET ,
1210+ _ => APPLICATION_SESSION_AUTHENTICATION_PATH_TESTNET ,
1211+ }
1212+ . into ( ) ;
1213+ root_derivation_path. 0 . extend ( & [
1214+ ChildNumber :: Hardened {
1215+ index : KeyDerivationType :: ECDSA . into ( ) ,
1216+ } ,
1217+ ChildNumber :: Hardened256 {
1218+ index : identity_id,
1219+ } ,
1220+ ChildNumber :: Hardened256 {
1221+ index : request_id,
1222+ } ,
1223+ ] ) ;
1224+ root_derivation_path
1225+ }
1226+
1227+ /// DIP-13 application encryption key path,
1228+ /// `m/9'/coin_type'/5'/7'/0'/identity_id'/contract_id'/key_purpose'`. The identity id and
1229+ /// contract id are DIP-14 256-bit hardened children, which only secp256k1 derivation defines,
1230+ /// so the key type level is always ECDSA (`0'`).
1231+ pub fn application_encryption_path (
1232+ network : Network ,
1233+ identity_id : [ u8 ; 32 ] ,
1234+ contract_id : [ u8 ; 32 ] ,
1235+ key_purpose : ApplicationKeyPurpose ,
1236+ ) -> Self {
1237+ let mut root_derivation_path: DerivationPath = match network {
1238+ Network :: Mainnet => APPLICATION_ENCRYPTION_PATH_MAINNET ,
1239+ _ => APPLICATION_ENCRYPTION_PATH_TESTNET ,
1240+ }
1241+ . into ( ) ;
1242+ root_derivation_path. 0 . extend ( & [
1243+ ChildNumber :: Hardened {
1244+ index : KeyDerivationType :: ECDSA . into ( ) ,
1245+ } ,
1246+ ChildNumber :: Hardened256 {
1247+ index : identity_id,
1248+ } ,
1249+ ChildNumber :: Hardened256 {
1250+ index : contract_id,
1251+ } ,
1252+ ChildNumber :: Hardened {
1253+ index : key_purpose. into ( ) ,
1254+ } ,
1255+ ] ) ;
1256+ root_derivation_path
1257+ }
1258+
11791259 pub fn derive_priv_ecdsa_for_master_seed (
11801260 & self ,
11811261 seed : & [ u8 ] ,
@@ -2676,6 +2756,136 @@ mod tests {
26762756 assert_eq ! ( path. to_string( ) , "m/9'/1'/5'/0'/1'/2'/3'" ) ;
26772757 }
26782758
2759+ #[ test]
2760+ fn test_application_session_authentication_path ( ) {
2761+ let path = DerivationPath :: application_session_authentication_path (
2762+ Network :: Mainnet ,
2763+ [ 0x01 ; 32 ] ,
2764+ [ 0x02 ; 32 ] ,
2765+ ) ;
2766+ assert_eq ! (
2767+ path. to_string( ) ,
2768+ format!( "m/9'/5'/5'/6'/0'/0x{}'/0x{}'" , "01" . repeat( 32 ) , "02" . repeat( 32 ) )
2769+ ) ;
2770+
2771+ let path = DerivationPath :: application_session_authentication_path (
2772+ Network :: Testnet ,
2773+ [ 0x01 ; 32 ] ,
2774+ [ 0x02 ; 32 ] ,
2775+ ) ;
2776+ assert_eq ! (
2777+ path. to_string( ) ,
2778+ format!( "m/9'/1'/5'/6'/0'/0x{}'/0x{}'" , "01" . repeat( 32 ) , "02" . repeat( 32 ) )
2779+ ) ;
2780+ }
2781+
2782+ #[ test]
2783+ fn test_application_encryption_path ( ) {
2784+ let path = DerivationPath :: application_encryption_path (
2785+ Network :: Mainnet ,
2786+ [ 0x01 ; 32 ] ,
2787+ [ 0x02 ; 32 ] ,
2788+ ApplicationKeyPurpose :: Encryption ,
2789+ ) ;
2790+ assert_eq ! (
2791+ path. to_string( ) ,
2792+ format!( "m/9'/5'/5'/7'/0'/0x{}'/0x{}'/1'" , "01" . repeat( 32 ) , "02" . repeat( 32 ) )
2793+ ) ;
2794+
2795+ let path = DerivationPath :: application_encryption_path (
2796+ Network :: Testnet ,
2797+ [ 0x01 ; 32 ] ,
2798+ [ 0x02 ; 32 ] ,
2799+ ApplicationKeyPurpose :: Decryption ,
2800+ ) ;
2801+ assert_eq ! (
2802+ path. to_string( ) ,
2803+ format!( "m/9'/1'/5'/7'/0'/0x{}'/0x{}'/2'" , "01" . repeat( 32 ) , "02" . repeat( 32 ) )
2804+ ) ;
2805+ }
2806+
2807+ /// Pinned public keys for the all-zero-entropy test mnemonic. A change here orphans registered
2808+ /// encryption keys, which wallets re-derive from seed. The uniform ids match the vectors
2809+ /// dashpay/platform pins; the non-uniform ones catch a byte-order or argument-order change.
2810+ #[ test]
2811+ fn test_application_key_vectors ( ) {
2812+ let seed = crate :: mnemonic:: Mnemonic :: from_phrase (
2813+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" ,
2814+ )
2815+ . unwrap ( )
2816+ . to_seed ( "" ) ;
2817+ let ascending: [ u8 ; 32 ] = core:: array:: from_fn ( |i| i as u8 ) ;
2818+ let descending: [ u8 ; 32 ] = core:: array:: from_fn ( |i| 0xff - i as u8 ) ;
2819+ let public_key_hex = |path : DerivationPath , network| {
2820+ hex:: encode (
2821+ path. derive_pub_ecdsa_for_master_seed ( & seed, network)
2822+ . unwrap ( )
2823+ . public_key
2824+ . serialize ( ) ,
2825+ )
2826+ } ;
2827+
2828+ let session = |identity_id, request_id| {
2829+ public_key_hex (
2830+ DerivationPath :: application_session_authentication_path (
2831+ Network :: Testnet ,
2832+ identity_id,
2833+ request_id,
2834+ ) ,
2835+ Network :: Testnet ,
2836+ )
2837+ } ;
2838+ let encryption = |identity_id, contract_id, key_purpose| {
2839+ public_key_hex (
2840+ DerivationPath :: application_encryption_path (
2841+ Network :: Mainnet ,
2842+ identity_id,
2843+ contract_id,
2844+ key_purpose,
2845+ ) ,
2846+ Network :: Mainnet ,
2847+ )
2848+ } ;
2849+
2850+ assert_eq ! (
2851+ session( [ 0x35 ; 32 ] , [ 0x6B ; 32 ] ) ,
2852+ "022c8b2e806244482374b1caf8306146dc03aad3b99a5954efd5e70a0eddd37a5d"
2853+ ) ;
2854+ assert_eq ! (
2855+ encryption( [ 0x35 ; 32 ] , [ 0x6B ; 32 ] , ApplicationKeyPurpose :: Encryption ) ,
2856+ "03e989de1b62f137231cc06659c810c17100becd1f5e23d5710faa7602769fe434"
2857+ ) ;
2858+ assert_eq ! (
2859+ session( ascending, descending) ,
2860+ "0278ad78c1a220924bf0e06e1111c61422efeace068f30efa825604b436970b230"
2861+ ) ;
2862+ assert_eq ! (
2863+ encryption( ascending, descending, ApplicationKeyPurpose :: Encryption ) ,
2864+ "03c4e462229f176595ec17e28e9096d3cfbc42496fb8ed4d48f8f58355d6105aa9"
2865+ ) ;
2866+ assert_eq ! (
2867+ encryption( ascending, descending, ApplicationKeyPurpose :: Decryption ) ,
2868+ "031fd0a3cfc01bebb89d43c2e12a6321c37ebdacb62c09495899e5b65a24fa5c99"
2869+ ) ;
2870+
2871+ // The builder's path is the documented string, byte order included.
2872+ let parsed: DerivationPath = format ! (
2873+ "m/9'/1'/5'/6'/0'/0x{}'/0x{}'" ,
2874+ hex:: encode( ascending) ,
2875+ hex:: encode( descending)
2876+ )
2877+ . parse ( )
2878+ . unwrap ( ) ;
2879+ assert_eq ! (
2880+ parsed,
2881+ DerivationPath :: application_session_authentication_path(
2882+ Network :: Testnet ,
2883+ ascending,
2884+ descending,
2885+ )
2886+ ) ;
2887+ }
2888+
26792889 #[ test]
26802890 fn test_derive_priv_ecdsa_for_master_seed ( ) {
26812891 let path = DerivationPath :: bip_44_account ( Network :: Mainnet , 0 ) ;
0 commit comments