@@ -10,6 +10,7 @@ import { PublicKey } from "./publicKey";
1010import { Signature } from "./signature" ;
1111import { convertSigningMessage } from "./utils" ;
1212import { AptosConfig } from "../../api" ;
13+ import { CKDPriv , deriveKey , HARDENED_OFFSET , isValidHardenedPath , mnemonicToSeed , splitPath } from "./hdKey" ;
1314
1415/**
1516 * Represents a SLH-DSA-SHA2-128s public key.
@@ -193,6 +194,17 @@ export class SlhDsaSha2128sPrivateKey extends Serializable implements PrivateKey
193194 */
194195 static readonly LENGTH : number = 48 ;
195196
197+ /**
198+ * The SLH-DSA-SHA2-128s key seed to use for BIP-32 compatibility
199+ * See more {@link https://github.com/satoshilabs/slips/blob/master/slip-0010.md}
200+ *
201+ * TODO: This is not standardized... AFAIK.
202+ *
203+ * @group Implementation
204+ * @category Serialization
205+ */
206+ static readonly SLIP_0010_SEED = "SLH-DSA-SHA2-128s seed" ;
207+
196208 /**
197209 * The 48-byte three seeds (SK seed + PRF seed + PK seed) used for serialization
198210 * @private
@@ -274,6 +286,61 @@ export class SlhDsaSha2128sPrivateKey extends Serializable implements PrivateKey
274286 return new SlhDsaSha2128sSignature ( signatureBytes ) ;
275287 }
276288
289+
290+ /**
291+ * Derives a private key from a mnemonic seed phrase using a specified BIP44 path.
292+ * To derive multiple keys from the same phrase, change the path
293+ *
294+ * IMPORTANT: SLH-DSA-SHA2-128s supports hardened derivation only, as it lacks a key homomorphism, making non-hardened derivation impossible.
295+ *
296+ * @param path - The BIP44 path used for key derivation.
297+ * @param mnemonics - The mnemonic seed phrase from which the key will be derived.
298+ * @throws Error if the provided path is not a valid hardened path.
299+ * @group Implementation
300+ * @category Serialization
301+ */
302+ static fromDerivationPath ( path : string , mnemonics : string ) : SlhDsaSha2128sPrivateKey {
303+ if ( ! isValidHardenedPath ( path ) ) {
304+ throw new Error ( `Invalid derivation path ${ path } ` ) ;
305+ }
306+ return SlhDsaSha2128sPrivateKey . fromDerivationPathInner ( path , mnemonicToSeed ( mnemonics ) ) ;
307+ }
308+
309+ /**
310+ * Derives a child private key from a given BIP44 path and seed.
311+ *
312+ * We derive our 48-byte SLH-DSA key (three 16-byte seeds) from:
313+ * - the 32-byte, BIP-32-derived, secret key
314+ * - the first 16 bytes of the BIP-32-derived chain code
315+ *
316+ * @param path - The BIP44 path used for key derivation.
317+ * @param seed - The seed phrase created by the mnemonics, represented as a Uint8Array.
318+ * @param offset - The offset used for key derivation, defaults to HARDENED_OFFSET.
319+ * @returns An instance of SlhDsaSha2128sPrivateKey derived from the specified path and seed.
320+ * @group Implementation
321+ * @category Serialization
322+ */
323+ private static fromDerivationPathInner ( path : string , seed : Uint8Array , offset = HARDENED_OFFSET ) : SlhDsaSha2128sPrivateKey {
324+ const { key, chainCode } = deriveKey ( SlhDsaSha2128sPrivateKey . SLIP_0010_SEED , seed ) ;
325+
326+ const segments = splitPath ( path ) . map ( ( el ) => parseInt ( el , 10 ) ) ;
327+
328+ // Derive the child key based on the path
329+ const { key : privateKey , chainCode : finalChainCode } = segments . reduce ( ( parentKeys , segment ) => CKDPriv ( parentKeys , segment + offset ) , {
330+ key,
331+ chainCode,
332+ } ) ;
333+
334+ const threeSeeds = new Uint8Array ( 48 ) ;
335+ threeSeeds . set ( privateKey , 0 ) ; // First 32 bytes from the derived secret key
336+
337+ // TODO: We would need to reason about the security of this.
338+ // e.g., is it okay to treat the chain code as public?
339+ threeSeeds . set ( finalChainCode . slice ( 0 , 16 ) , 32 ) ; // Last 16 bytes from the derived chain code
340+
341+ return new SlhDsaSha2128sPrivateKey ( threeSeeds , false ) ;
342+ }
343+
277344 /**
278345 * Derive the SlhDsaSha2128sPublicKey from this private key.
279346 * The public key is extracted from the pre-computed secret key.
0 commit comments