How can I set my own ed25519 key?

Now that ipfs is using ed25519 by default… Instead of using “ipfs init” to generate a random identity, I would like to create and set my own ed25519 key pair. Is it possible?

Thanks

There is no external CLI command to set the libp2p node’s private key. If you wanted to take an existing ed25519 key and use it you could modify your config file appropriately to generate libp2p keys according to the spec.

If you’re using the keys for IPNS publishing (instead of the libp2p identity key) you can import/export keys with the new ipfs key import/export

1 Like

ipfs key rotate or https://github.com/whyrusleeping/ipfs-key

1 Like

I would like to convert pubsec (base58) keys from https://cesium.app

Type: PubSec
Version: 1
pub: BNJDrwA9tRQAnCu82JUo6BnXCU8WFGHMjzPYG7r7JBUv
sec: 55kzhEqc9gsdJqDsG5Y5cAUoLfXCg8GvBh9GivRSsxMKEQcotRwmt9F35yERpT3WeXbtwYkhVoGAZMsXEFFD1u7x

compared to ~/.ipfs/config

{
  "Identity": {
    "PeerID": "12D3KooWNVNzjKcgJKLQHAdgVLVjNQgZYNFExxaCEGtWzA1dopWw",
    "PrivKey": "CAESQGEmzRPrd4P4FKI9Y8bzepjmXyf+8WFVzeSYuKxoaozlvErUhOLxFZc4rceGYXjngqvsILtAxxfyrnYmJcvSx5Q="
  },

It seems to have some representative base difference… base64, multihash prefix?
any help is welcome :sunny:

@zicmama I wrote a simple Python script based on your keys, it can illustrate the principles of working with these keys. Maybe it will help you. The resulting keys promised to be compatible with IPFS because they are definitely compatible with ED25519 specification.

import base58
import base64
import cryptography.hazmat.primitives.asymmetric.ed25519 as ed25519
from cryptography.hazmat.primitives import serialization

shared_key = 'BNJDrwA9tRQAnCu82JUo6BnXCU8WFGHMjzPYG7r7JBUv'
secure_key = '55kzhEqc9gsdJqDsG5Y5cAUoLfXCg8GvBh9GivRSsxMKEQcotRwmt9F35yERpT3WeXbtwYkhVoGAZMsXEFFD1u7x'

decoded_shared = base58.b58decode(shared_key)
decoded_secure = base58.b58decode(secure_key)

print(decoded_shared)
print(decoded_secure)

ipfs_shared = ed25519.Ed25519PublicKey.from_public_bytes(decoded_shared)
ipfs_secure = ed25519.Ed25519PrivateKey.from_private_bytes(decoded_secure[:32])

string_ipfs_shared = base64.b64encode(ipfs_shared.public_bytes(encoding=serialization.Encoding.Raw,
                                                               format=serialization.PublicFormat.Raw))
string_ipfs_secure = base64.b64encode(ipfs_secure.private_bytes(encoding=serialization.Encoding.Raw,
                                                                format=serialization.PrivateFormat.Raw,
                                                                encryption_algorithm=serialization.NoEncryption()))
print(string_ipfs_shared.decode('ascii'))
print(string_ipfs_secure.decode('ascii'))
print(base64.b64encode(decoded_secure).decode('ascii'))

It produces following output:

b"\x9a\t\x89\xf7\xd2\xb6\xe4\xbb\x16\xe9\xa42\x05rA\x06\x8e<W\xaa\xdc\x1fU\xb8\x12\xf4\x04'\x06\xc6jc"
b"\xcc+\xeb\xf05\xc4\xe9\x03\xbdR\xe8\xba\x07\x96\x89a\x0f\xb8\xaa,\xb4\xdfl\xec\xf4%\x9d\x82\xea+\xba\xf8\x9a\t\x89\xf7\xd2\xb6\xe4\xbb\x16\xe9\xa42\x05rA\x06\x8e<W\xaa\xdc\x1fU\xb8\x12\xf4\x04'\x06\xc6jc"
mgmJ99K25LsW6aQyBXJBBo48V6rcH1W4EvQEJwbGamM=
zCvr8DXE6QO9Uui6B5aJYQ+4qiy032zs9CWdguoruvg=
zCvr8DXE6QO9Uui6B5aJYQ+4qiy032zs9CWdguoruviaCYn30rbkuxbppDIFckEGjjxXqtwfVbgS9AQnBsZqYw==

Please note that the IPFS node supports key specification where the binary encoded public ED25519 key (32 bytes) is attached directly to the private key (also 32 bytes length).

Sorry, but I have not already tested the keys on the real node.

@twdragon you’re code seems great, but doesn’t ipfs add a prefix (before it was “Qm”)

I was already using bash commands,

echo 55kzhEqc9gsdJqDsG5Y5cAUoLfXCg8GvBh9GivRSsxMKEQcotRwmt9F35yERpT3WeXbtwYkhVoGAZMsXEFFD1u7x | base58 -d | base64
zCvr8DXE6QO9Uui6B5aJYQ+4qiy032zs9CWdguoruviaCYn30rbkuxbppDIFckEGjjxXqtwfVbgS9AQnBsZqYw==

Do I just need to add “12” at the begining of pubkey to make my PeerID ?

After some investigation, I found how to fake the IPFS node’s ED25519 private key and PeerID with your own ones. IPFS use Protocol Buffers, so libprotobuf and the recent protoc are needed. At first, obtain Python code from message definition:

wget https://github.com/libp2p/go-libp2p-core/raw/master/crypto/pb/crypto.proto
protoc --python_out=. crypto.proto

which will produce crypto_pb2.py Python library.

Then run the Python script (based on the previous):

import base58
import base64
import cryptography.hazmat.primitives.asymmetric.ed25519 as ed25519
from cryptography.hazmat.primitives import serialization
import crypto_pb2
# Capturing keys (from any source, Base58 strings here)
shared_key = 'BNJDrwA9tRQAnCu82JUo6BnXCU8WFGHMjzPYG7r7JBUv'
secure_key = '55kzhEqc9gsdJqDsG5Y5cAUoLfXCg8GvBh9GivRSsxMKEQcotRwmt9F35yERpT3WeXbtwYkhVoGAZMsXEFFD1u7x'
# Decoding keys
decoded_shared = base58.b58decode(shared_key)
decoded_secure = base58.b58decode(secure_key)
ipfs_shared = ed25519.Ed25519PublicKey.from_public_bytes(decoded_shared)
ipfs_secure = ed25519.Ed25519PrivateKey.from_private_bytes(decoded_secure[:32])
ipfs_shared_bytes = ipfs_shared.public_bytes(encoding=serialization.Encoding.Raw,
                                             format=serialization.PublicFormat.Raw)
ipfs_secure_bytes = ipfs_secure.private_bytes(encoding=serialization.Encoding.Raw,
                                              format=serialization.PrivateFormat.Raw,
                                              encryption_algorithm=serialization.NoEncryption())
# Formulating PeerID
ipfs_pid = base58.b58encode(b'\x00$\x08\x01\x12 ' + ipfs_shared_bytes)
print('Peer ID: {}'.format(ipfs_pid.decode('ascii')))
# Serializing private key in IPFS-native mode, the private key contains public one
pkey = crypto_pb2.PrivateKey()
pkey.Type = crypto_pb2.KeyType.Ed25519
pkey.Data = ipfs_secure_bytes + ipfs_shared_bytes
print('Private key: {}'.format(base64.b64encode(pkey.SerializeToString()).decode('ascii')))

This script will produce the keys:

Peer ID: 12D3KooWLBfPc4js5KffC6QYeuE8rysMgoB9GxMqhakZe711C8Cr
Private key: CAESQMwr6/A1xOkDvVLougeWiWEPuKostN9s7PQlnYLqK7r4mgmJ99K25LsW6aQyBXJBBo48V6rcH1W4EvQEJwbGamM=

These keys could be substituted into the IPFS node config file instead of the original ones.

1 Like
Traceback (most recent call last):
  File "mykey.py", line 24, in <module>
    pkey.Type = crypto_pb2.KeyType.Ed25519
AttributeError: 'EnumTypeWrapper' object has no attribute 'Ed25519'

@twdragon what do you think i am missing?

sudo apt install python3-dev 
sudo pip3 install cryptography Ed25519

@zicmama did you use the recent version of the .proto file from Github?

i followed

wget https://github.com/libp2p/go-libp2p-core/raw/master/crypto/pb/crypto.proto
sudo apt install protobuf-compiler
protoc --python_out=. crypto.proto

so it must be

@zicmama can you list the crypto_pb2.py file here? It should not be very long. My one looks like this:

# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: crypto.proto

from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor.FileDescriptor(
  name='crypto.proto',
  package='crypto.pb',
  syntax='proto2',
  serialized_options=b'Z*github.com/libp2p/go-libp2p-core/crypto/pb',
  create_key=_descriptor._internal_create_key,
  serialized_pb=b'\n\x0c\x63rypto.proto\x12\tcrypto.pb\";\n\tPublicKey\x12 \n\x04Type\x18\x01 \x02(\x0e\x32\x12.crypto.pb.KeyType\x12\x0c\n\x04\x44\x61ta\x18\x02 \x02(\x0c\"<\n\nPrivateKey\x12 \n\x04Type\x18\x01 \x02(\x0e\x32\x12.crypto.pb.KeyType\x12\x0c\n\x04\x44\x61ta\x18\x02 \x02(\x0c*9\n\x07KeyType\x12\x07\n\x03RSA\x10\x00\x12\x0b\n\x07\x45\x64\x32\x35\x35\x31\x39\x10\x01\x12\r\n\tSecp256k1\x10\x02\x12\t\n\x05\x45\x43\x44SA\x10\x03\x42,Z*github.com/libp2p/go-libp2p-core/crypto/pb'
)

_KEYTYPE = _descriptor.EnumDescriptor(
  name='KeyType',
  full_name='crypto.pb.KeyType',
  filename=None,
  file=DESCRIPTOR,
  create_key=_descriptor._internal_create_key,
  values=[
    _descriptor.EnumValueDescriptor(
      name='RSA', index=0, number=0,
      serialized_options=None,
      type=None,
      create_key=_descriptor._internal_create_key),
    _descriptor.EnumValueDescriptor(
      name='Ed25519', index=1, number=1,
      serialized_options=None,
      type=None,
      create_key=_descriptor._internal_create_key),
    _descriptor.EnumValueDescriptor(
      name='Secp256k1', index=2, number=2,
      serialized_options=None,
      type=None,
      create_key=_descriptor._internal_create_key),
    _descriptor.EnumValueDescriptor(
      name='ECDSA', index=3, number=3,
      serialized_options=None,
      type=None,
      create_key=_descriptor._internal_create_key),
  ],
  containing_type=None,
  serialized_options=None,
  serialized_start=150,
  serialized_end=207,
)
_sym_db.RegisterEnumDescriptor(_KEYTYPE)

KeyType = enum_type_wrapper.EnumTypeWrapper(_KEYTYPE)
RSA = 0
Ed25519 = 1
Secp256k1 = 2
ECDSA = 3



_PUBLICKEY = _descriptor.Descriptor(
  name='PublicKey',
  full_name='crypto.pb.PublicKey',
  filename=None,
  file=DESCRIPTOR,
  containing_type=None,
  create_key=_descriptor._internal_create_key,
  fields=[
    _descriptor.FieldDescriptor(
      name='Type', full_name='crypto.pb.PublicKey.Type', index=0,
      number=1, type=14, cpp_type=8, label=2,
      has_default_value=False, default_value=0,
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
    _descriptor.FieldDescriptor(
      name='Data', full_name='crypto.pb.PublicKey.Data', index=1,
      number=2, type=12, cpp_type=9, label=2,
      has_default_value=False, default_value=b"",
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
  ],
  extensions=[
  ],
  nested_types=[],
  enum_types=[
  ],
  serialized_options=None,
  is_extendable=False,
  syntax='proto2',
  extension_ranges=[],
  oneofs=[
  ],
  serialized_start=27,
  serialized_end=86,
)


_PRIVATEKEY = _descriptor.Descriptor(
  name='PrivateKey',
  full_name='crypto.pb.PrivateKey',
  filename=None,
  file=DESCRIPTOR,
  containing_type=None,
  create_key=_descriptor._internal_create_key,
  fields=[
    _descriptor.FieldDescriptor(
      name='Type', full_name='crypto.pb.PrivateKey.Type', index=0,
      number=1, type=14, cpp_type=8, label=2,
      has_default_value=False, default_value=0,
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
    _descriptor.FieldDescriptor(
      name='Data', full_name='crypto.pb.PrivateKey.Data', index=1,
      number=2, type=12, cpp_type=9, label=2,
      has_default_value=False, default_value=b"",
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
  ],
  extensions=[
  ],
  nested_types=[],
  enum_types=[
  ],
  serialized_options=None,
  is_extendable=False,
  syntax='proto2',
  extension_ranges=[],
  oneofs=[
  ],
  serialized_start=88,
  serialized_end=148,
)

_PUBLICKEY.fields_by_name['Type'].enum_type = _KEYTYPE
_PRIVATEKEY.fields_by_name['Type'].enum_type = _KEYTYPE
DESCRIPTOR.message_types_by_name['PublicKey'] = _PUBLICKEY
DESCRIPTOR.message_types_by_name['PrivateKey'] = _PRIVATEKEY
DESCRIPTOR.enum_types_by_name['KeyType'] = _KEYTYPE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)

PublicKey = _reflection.GeneratedProtocolMessageType('PublicKey', (_message.Message,), {
  'DESCRIPTOR' : _PUBLICKEY,
  '__module__' : 'crypto_pb2'
  # @@protoc_insertion_point(class_scope:crypto.pb.PublicKey)
  })
_sym_db.RegisterMessage(PublicKey)

PrivateKey = _reflection.GeneratedProtocolMessageType('PrivateKey', (_message.Message,), {
  'DESCRIPTOR' : _PRIVATEKEY,
  '__module__' : 'crypto_pb2'
  # @@protoc_insertion_point(class_scope:crypto.pb.PrivateKey)
  })
_sym_db.RegisterMessage(PrivateKey)


DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: crypto.proto

import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor.FileDescriptor(
  name='crypto.proto',
  package='crypto.pb',
  syntax='proto2',
  serialized_pb=_b('\n\x0c\x63rypto.proto\x12\tcrypto.pb\";\n\tPublicKey\x12 \n\x04Type\x18\x01 \x02(\x0e\x32\x12.crypto.pb.KeyType\x12\x0c\n\x04\x44\x61ta\x18\x02 \x02(\x0c\"<\n\nPrivateKey\x12 \n\x04Type\x18\x01 \x02(\x0e\x32\x12.crypto.pb.KeyType\x12\x0c\n\x04\x44\x61ta\x18\x02 \x02(\x0c*9\n\x07KeyType\x12\x07\n\x03RSA\x10\x00\x12\x0b\n\x07\x45\x64\x32\x35\x35\x31\x39\x10\x01\x12\r\n\tSecp256k1\x10\x02\x12\t\n\x05\x45\x43\x44SA\x10\x03\x42,Z*github.com/libp2p/go-libp2p-core/crypto/pb')
)
_sym_db.RegisterFileDescriptor(DESCRIPTOR)

_KEYTYPE = _descriptor.EnumDescriptor(
  name='KeyType',
  full_name='crypto.pb.KeyType',
  filename=None,
  file=DESCRIPTOR,
  values=[
    _descriptor.EnumValueDescriptor(
      name='RSA', index=0, number=0,
      options=None,
      type=None),
    _descriptor.EnumValueDescriptor(
      name='Ed25519', index=1, number=1,
      options=None,
      type=None),
    _descriptor.EnumValueDescriptor(
      name='Secp256k1', index=2, number=2,
      options=None,
      type=None),
    _descriptor.EnumValueDescriptor(
      name='ECDSA', index=3, number=3,
      options=None,
      type=None),
  ],
  containing_type=None,
  options=None,
  serialized_start=150,
  serialized_end=207,
)
_sym_db.RegisterEnumDescriptor(_KEYTYPE)

KeyType = enum_type_wrapper.EnumTypeWrapper(_KEYTYPE)
RSA = 0
Ed25519 = 1
Secp256k1 = 2
ECDSA = 3



_PUBLICKEY = _descriptor.Descriptor(
  name='PublicKey',
  full_name='crypto.pb.PublicKey',
  filename=None,
  file=DESCRIPTOR,
  containing_type=None,
  fields=[
    _descriptor.FieldDescriptor(
      name='Type', full_name='crypto.pb.PublicKey.Type', index=0,
      number=1, type=14, cpp_type=8, label=2,
      has_default_value=False, default_value=0,
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      options=None),
    _descriptor.FieldDescriptor(
      name='Data', full_name='crypto.pb.PublicKey.Data', index=1,
      number=2, type=12, cpp_type=9, label=2,
      has_default_value=False, default_value=_b(""),
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      options=None),
  ],
  extensions=[
  ],
  nested_types=[],
  enum_types=[
  ],
  options=None,
  is_extendable=False,
  syntax='proto2',
  extension_ranges=[],
  oneofs=[
  ],
  serialized_start=27,
  serialized_end=86,
)


_PRIVATEKEY = _descriptor.Descriptor(
  name='PrivateKey',
  full_name='crypto.pb.PrivateKey',
  filename=None,
  file=DESCRIPTOR,
  containing_type=None,
  fields=[
    _descriptor.FieldDescriptor(
      name='Type', full_name='crypto.pb.PrivateKey.Type', index=0,
      number=1, type=14, cpp_type=8, label=2,
      has_default_value=False, default_value=0,
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      options=None),
    _descriptor.FieldDescriptor(
      name='Data', full_name='crypto.pb.PrivateKey.Data', index=1,
      number=2, type=12, cpp_type=9, label=2,
      has_default_value=False, default_value=_b(""),
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      options=None),
  ],
  extensions=[
  ],
  nested_types=[],
  enum_types=[
  ],
  options=None,
  is_extendable=False,
  syntax='proto2',
  extension_ranges=[],
  oneofs=[
  ],
  serialized_start=88,
  serialized_end=148,
)

_PUBLICKEY.fields_by_name['Type'].enum_type = _KEYTYPE
_PRIVATEKEY.fields_by_name['Type'].enum_type = _KEYTYPE
DESCRIPTOR.message_types_by_name['PublicKey'] = _PUBLICKEY
DESCRIPTOR.message_types_by_name['PrivateKey'] = _PRIVATEKEY
DESCRIPTOR.enum_types_by_name['KeyType'] = _KEYTYPE

PublicKey = _reflection.GeneratedProtocolMessageType('PublicKey', (_message.Message,), dict(
  DESCRIPTOR = _PUBLICKEY,
  __module__ = 'crypto_pb2'
  # @@protoc_insertion_point(class_scope:crypto.pb.PublicKey)
  ))
_sym_db.RegisterMessage(PublicKey)

PrivateKey = _reflection.GeneratedProtocolMessageType('PrivateKey', (_message.Message,), dict(
  DESCRIPTOR = _PRIVATEKEY,
  __module__ = 'crypto_pb2'
  # @@protoc_insertion_point(class_scope:crypto.pb.PrivateKey)
  ))
_sym_db.RegisterMessage(PrivateKey)


DESCRIPTOR.has_options = True
DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('Z*github.com/libp2p/go-libp2p-core/crypto/pb'))
# @@protoc_insertion_point(module_scope)

@zicmama I tested your file, it works fine for me. Please check if there is another python-protobuf version that can override the names generated by your protoc. This kind of errors you often could encounter when you have several different versions of libprotobuf installed on the same system.

You may be right. My dev machine could be messy on this point.
I am going to try on a fresh one.

I am already using duniterpy to produce my keys from credentials (salt/pepper) that could have some libraries involved

With

sudo apt install python3-dev protobuf-compiler 
sudo pip3 install cryptography Ed25519 base58 google protobuf

On a fresh install, the script is running!!
@twdragon thank you so much

I am really bad at python, but i could sew it in the bash

@twdragon I’d like do reverse “IPFS peer ID” into pub (duniter base58 format) to identify connections in my swarm. I take the opportunity to ask you help? How would you convert it with python?

In a bit of time I can try to help you. So you need to extract peer ID into base58-encoded CID, right? Do you want to extract peerID into pubsub?

Exactly. Your help would be wonderful

I am not sure what you question means…
But I am confused with the “localhost:8080/ipfs/Qm_ressource_identifier” transformation into “ipfs_subdomain_identifier.localhost:8080/” happening in latest release. Is it what you are talking about?

@zicmama Did you try using the libp2p.peer.id module, specifically the to_base58() method?

@AlexxNica @twdragon

#!/usr/bin/env python3

import sys, base58

ID = sys.argv[1]
hexFmt = base58.b58decode(ID)
noTag = hexFmt[6:]
b58Key = base58.b58encode(noTag).decode()

print(b58Key)

got it

thanks a lot

1 Like