go-libtor icon indicating copy to clipboard operation
go-libtor copied to clipboard

How is it possible to persist V3 .onion address?

Open qwertyqop opened this issue 3 years ago • 4 comments

I would like to keep my onion address static after I close the program and reopen it. this is done with V2 addressed as described in issue #19, however it is not applicable with V3 addresses. how do I make V3 addresses persistent after reopening the program?

qwertyqop avatar Mar 26 '22 16:03 qwertyqop

Working off of the libtor example, observe these comments and changes.

import  (
...
        "github.com/cretz/bine/torutil/ed25519"
         b64 "encoding/base64"
)

...

    // $ dd if=/dev/urandom bs=64 count=1 status=none| base64 -w100
    // OR if you generated a vanity address, use this to dump the last 64 bytes from a stored onion-V3 :
    // $ dd if=hs_ed25519_secret_key skip=32 bs=1 status=none | base64 -w 100
    _b64Ed25519 := "OUyeOZtOT8JgE2JrjDsoFTCGWYdz+rc1CTEbgGtD6swEQcD1jmWkWUrkmFiRLkcBVvio5/yUyRJ24fqNyei57Q=="
    _key, _ := b64.StdEncoding.DecodeString(_b64Ed25519)
    
    // Create an onion service to listen on any port but show as 80
    onion, err := t.Listen(ctx, &tor.ListenConf{RemotePorts: []int{80}, Version3: true, Key: ed25519.PrivateKey(_key)})
    if err != nil {
        log.Panicf("Failed to create onion service: %v", err)
    }
    defer onion.Close()

Danukeru avatar Apr 16 '22 10:04 Danukeru

As @Danukeru's example shows, you can utilize a vanity address as well. There is a nice little Go package here that can assist with generating that if you'd like to specify a reusable prefix for your V3 address.

ciehanski avatar Jul 03 '22 18:07 ciehanski

I use this scheme based on ciehanski post.

To create a key,

func createKey(path string) (crypto.PrivateKey, error) {
	path = strings.TrimSuffix(path, "hs_ed25519_secret_key")
	err := os.MkdirAll(path, 0700)
	if err != nil {
		return nil, err
	}

	publicKey, secretKey, err := ed25519.GenerateKey(nil)
	if err != nil {
		return nil, err
	}
	onionAddress := encodePublicKey(publicKey)

	expandedSecretKey := expandSecretKey(secretKey)
	secretKeyFile := append([]byte("== ed25519v1-secret: type0 ==\x00\x00\x00"), expandedSecretKey[:]...)
	secretKeyPath := filepath.Join(path, "/hs_ed25519_secret_key")
	err = os.WriteFile(secretKeyPath, secretKeyFile, 0600)
	if err != nil {
		return nil, err
	}
	publicKeyFile := append([]byte("== ed25519v1-public: type0 ==\x00\x00\x00"), publicKey...)
	publicKeyPath := filepath.Join(path, "/hs_ed25519_public_key")
	err = os.WriteFile(publicKeyPath, publicKeyFile, 0600)
	if err != nil {
		os.Remove(secretKeyPath)
		return nil, err
	}
	hostnameFile := []byte(onionAddress + ".onion\n")
	hostnamePath := filepath.Join(path, "/hostname")
	err = os.WriteFile(hostnamePath, hostnameFile, 0600)
	if err != nil {
		os.Remove(secretKeyPath)
		os.Remove(publicKeyPath)
		return nil, err
	}
	return bed25519.PrivateKey(expandedSecretKey[:]), nil
}
func expandSecretKey(secretKey ed25519.PrivateKey) [64]byte {
	hash := sha512.Sum512(secretKey[:32])
	hash[0] &= 248
	hash[31] &= 127
	hash[31] |= 64
	return hash
}
func encodePublicKey(publicKey ed25519.PublicKey) string {
	// checksum = H(".onion checksum" || pubkey || version)
	var checksumBytes bytes.Buffer
	checksumBytes.Write([]byte(".onion checksum"))
	checksumBytes.Write([]byte(publicKey))
	checksumBytes.Write([]byte{0x03})
	checksum := sha3.Sum256(checksumBytes.Bytes())

	// onion_address = base32(pubkey || checksum || version)
	var onionAddressBytes bytes.Buffer
	onionAddressBytes.Write([]byte(publicKey))
	onionAddressBytes.Write([]byte(checksum[:2]))
	onionAddressBytes.Write([]byte{0x03})
	onionAddress := base32.StdEncoding.EncodeToString(onionAddressBytes.Bytes())

	return strings.ToLower(onionAddress)
}

to load a key

func loadKey(path string) (crypto.PrivateKey, error) {
	var key crypto.PrivateKey
	if path != "" {
		if !strings.HasSuffix(path, "hs_ed25519_secret_key") {
			path = filepath.Join(path, "hs_ed25519_secret_key")
		}
		d, err := os.ReadFile(path)
		if err != nil {
			return nil, fmt.Errorf("failed to load the private key: %v", err)
		}
		d = bytes.TrimPrefix(d, []byte("== ed25519v1-secret: type0 ==\x00\x00\x00"))
		key = bed25519.PrivateKey(d)
	}
	return key, nil
}

The bed25519 import is github.com/cretz/bine/torutil/ed25519

Then I give the key to the Tor process creator and it works upon restart. smooth.

mh-cbon avatar Aug 15 '22 21:08 mh-cbon