@@ -439,6 +439,7 @@ async function deriveAllBlockchainAddresses(wif) {
439439 xlm : null ,
440440 sol : null ,
441441 ada : null ,
442+ tor : null ,
442443 } ;
443444
444445 try {
@@ -512,10 +513,127 @@ async function deriveAllBlockchainAddresses(wif) {
512513 } catch ( e ) {
513514 console . warn ( "ADA derivation failed:" , e ) ;
514515 }
516+ try {
517+ const torKeys = convertWIFtoTOR ( wif ) ;
518+ if ( torKeys ) {
519+ addresses . tor = torKeys . address ;
520+ }
521+ } catch ( e ) {
522+ console . warn ( "TOR derivation failed:" , e ) ;
523+ }
515524
516525 return addresses ;
517526}
518527
528+ /**
529+ * Convert WIF private key to TOR v3 Onion address
530+ * Uses Ed25519 keypair and js-sha3 for checksum
531+ * @param {string } wif - WIF format private key
532+ * @returns {Object|null } Object containing onion address, public key, and Tor files or null on error
533+ */
534+ function convertWIFtoTOR ( wif ) {
535+ try {
536+ const hexToBytes = ( hex ) => new Uint8Array ( Crypto . util . hexToBytes ( hex ) ) ;
537+
538+ // 1. WIF -> 32-byte seed
539+ const privKeyHex = bitjs . wif2privkey ( wif ) . privkey ;
540+ const seed = hexToBytes ( privKeyHex . substring ( 0 , 64 ) ) ;
541+
542+ // 2. Expand the seed using SHA-512 (Tor protocol style)
543+ const hash = nacl . hash ( seed ) ; // Returns 64-byte Uint8Array
544+
545+ const secretScalar = hash . slice ( 0 , 32 ) ;
546+ const rightHalfRH = hash . slice ( 32 , 64 ) ;
547+
548+ // 3. "Clamp" the secret scalar (Ed25519 standard requirement)
549+ secretScalar [ 0 ] &= 248 ;
550+ secretScalar [ 31 ] &= 127 ;
551+ secretScalar [ 31 ] |= 64 ;
552+
553+ // 4. Derive Public Key from the Clamped Secret Scalar
554+ const kp = nacl . sign . keyPair . fromSeed ( seed ) ;
555+ const publicKey = kp . publicKey ;
556+
557+ // 5. Corrected Tor headers
558+ const secretHeader = new Uint8Array ( [
559+ 0x3d , 0x3d , 0x20 , 0x65 , 0x64 , 0x32 , 0x35 , 0x35 ,
560+ 0x31 , 0x39 , 0x76 , 0x31 , 0x2d , 0x73 , 0x65 , 0x63 ,
561+ 0x72 , 0x65 , 0x74 , 0x3a , 0x20 , 0x74 , 0x79 , 0x70 ,
562+ 0x65 , 0x30 , 0x20 , 0x3d , 0x3d , 0x0a , 0x00 , 0x00
563+ ] ) ;
564+ const publicHeader = new Uint8Array ( [
565+ 0x3d , 0x3d , 0x20 , 0x65 , 0x64 , 0x32 , 0x35 , 0x35 ,
566+ 0x31 , 0x39 , 0x76 , 0x31 , 0x2d , 0x70 , 0x75 , 0x62 ,
567+ 0x6c , 0x69 , 0x63 , 0x3a , 0x20 , 0x74 , 0x79 , 0x70 ,
568+ 0x65 , 0x30 , 0x20 , 0x3d , 0x3d , 0x0a , 0x00 , 0x00
569+ ] ) ;
570+
571+ // 6. Build genuine Tor secret file layout
572+ const secretFile = new Uint8Array ( 96 ) ;
573+ secretFile . set ( secretHeader , 0 ) ;
574+ secretFile . set ( secretScalar , 32 ) ;
575+ secretFile . set ( rightHalfRH , 64 ) ;
576+
577+ // 7. Build Tor public file layout
578+ const publicFile = new Uint8Array ( 64 ) ;
579+ publicFile . set ( publicHeader , 0 ) ;
580+ publicFile . set ( publicKey , 32 ) ;
581+
582+ // 8. Generate Onion address
583+ const alphabet = "abcdefghijklmnopqrstuvwxyz234567" ;
584+
585+ const prefix = new TextEncoder ( ) . encode ( ".onion checksum" ) ;
586+ const version = new Uint8Array ( [ 0x03 ] ) ;
587+
588+ const checksumInput = new Uint8Array ( prefix . length + publicKey . length + version . length ) ;
589+ checksumInput . set ( prefix , 0 ) ;
590+ checksumInput . set ( publicKey , prefix . length ) ;
591+ checksumInput . set ( version , prefix . length + publicKey . length ) ;
592+
593+ // SHA3-256 (requires js-sha3 library)
594+ const hashHex = sha3_256 ( checksumInput ) ;
595+
596+ const checksum = new Uint8Array ( [
597+ parseInt ( hashHex . substring ( 0 , 2 ) , 16 ) ,
598+ parseInt ( hashHex . substring ( 2 , 4 ) , 16 )
599+ ] ) ;
600+
601+ const onionBytes = new Uint8Array ( publicKey . length + checksum . length + version . length ) ;
602+ onionBytes . set ( publicKey , 0 ) ;
603+ onionBytes . set ( checksum , publicKey . length ) ;
604+ onionBytes . set ( version , publicKey . length + checksum . length ) ;
605+
606+ let bits = 0 ;
607+ let value = 0 ;
608+ let output = "" ;
609+
610+ for ( const b of onionBytes ) {
611+ value = ( value << 8 ) | b ;
612+ bits += 8 ;
613+ while ( bits >= 5 ) {
614+ output += alphabet [ ( value >>> ( bits - 5 ) ) & 31 ] ;
615+ bits -= 5 ;
616+ }
617+ }
618+
619+ if ( bits > 0 ) {
620+ output += alphabet [ ( value << ( 5 - bits ) ) & 31 ] ;
621+ }
622+
623+ const onionAddress = output . toLowerCase ( ) + ".onion" ;
624+
625+ return {
626+ address : onionAddress ,
627+ publicKey,
628+ secretFile,
629+ publicFile
630+ } ;
631+ } catch ( e ) {
632+ console . error ( "WIF to TOR conversion error:" , e ) ;
633+ return null ;
634+ }
635+ }
636+
519637/**
520638 * Convert WIF private key to DOGE (Dogecoin) address
521639 * Uses secp256k1 with version byte 0x1e (30)
0 commit comments