channel export import
This commit is contained in:
parent
1b35cef77a
commit
6d462ad6d8
4 changed files with 96 additions and 5 deletions
|
@ -2122,7 +2122,7 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
)
|
||||
|
||||
@requires(WALLET_COMPONENT)
|
||||
async def jsonrpc_channel_export(self, claim_id):
|
||||
async def jsonrpc_channel_export(self, claim_id, password=None, account_id=None, insecure=False):
|
||||
"""
|
||||
Export serialized channel signing information for a given certificate claim id
|
||||
|
||||
|
@ -2135,11 +2135,12 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
Returns:
|
||||
(str) Serialized certificate information
|
||||
"""
|
||||
account = self.get_account_or_default(account_id)
|
||||
|
||||
return await self.wallet_manager.export_certificate_info(claim_id)
|
||||
return await self.wallet_manager.export_certificate_info(claim_id, account, password, insecure)
|
||||
|
||||
@requires(WALLET_COMPONENT)
|
||||
async def jsonrpc_channel_import(self, serialized_certificate_info):
|
||||
async def jsonrpc_channel_import(self, serialized_certificate_info, password=None, account_id=None):
|
||||
"""
|
||||
Import serialized channel signing information (to allow signing new claims to the channel)
|
||||
|
||||
|
@ -2152,8 +2153,9 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
Returns:
|
||||
(dict) Result dictionary
|
||||
"""
|
||||
account = self.get_account_or_default(account_id)
|
||||
|
||||
return await self.wallet_manager.import_certificate_info(serialized_certificate_info)
|
||||
return await self.wallet_manager.import_certificate_info(serialized_certificate_info, password, account)
|
||||
|
||||
STREAM_DOC = """
|
||||
Create, update, abandon, list and inspect your stream claims.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
import json
|
||||
import logging
|
||||
from binascii import unhexlify
|
||||
from binascii import unhexlify, hexlify
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
@ -17,6 +17,9 @@ from lbrynet.wallet.dewies import dewies_to_lbc
|
|||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_NO_PASSWORD_USED_BYTES = b'00'
|
||||
_PASSWORD_USED_BYTES = b'01'
|
||||
|
||||
|
||||
class LbryWalletManager(BaseWalletManager):
|
||||
|
||||
|
@ -301,6 +304,58 @@ class LbryWalletManager(BaseWalletManager):
|
|||
history.append(item)
|
||||
return history
|
||||
|
||||
async def export_certificate_info(self, claim_id, account, password=None, insecure=False):
|
||||
if password is None and not insecure:
|
||||
raise ValueError(
|
||||
"Password not provided. If you wish to export channel without a password, please use the "
|
||||
"--insecure flag"
|
||||
)
|
||||
|
||||
if password is not None and insecure:
|
||||
raise ValueError(
|
||||
"Password and insecure flag cannot be provided together. Please remove the insecure flag"
|
||||
)
|
||||
|
||||
try:
|
||||
channel_txo = (await account.get_channels(claim_id=claim_id, limit=1))[0]
|
||||
private_key_str = channel_txo.pem_to_private_key_str(channel_txo.private_key)
|
||||
except Exception:
|
||||
raise LookupError(f"Cannot retrieve private key for channel id: {claim_id}")
|
||||
|
||||
if not password:
|
||||
serialized_certificate_info = private_key_str + _NO_PASSWORD_USED_BYTES
|
||||
else:
|
||||
encrypted_private_key = self.encrypt_private_key_with_password(private_key_str, password)
|
||||
serialized_certificate_info = encrypted_private_key + _PASSWORD_USED_BYTES
|
||||
|
||||
x = hexlify(serialized_certificate_info).decode()
|
||||
return x
|
||||
|
||||
async def import_certificate_info(self, serialized_certificate_info, password, account):
|
||||
serialized_certificate_info = (unhexlify(serialized_certificate_info.encode()))
|
||||
|
||||
if password is None and serialized_certificate_info.endswith(_PASSWORD_USED_BYTES):
|
||||
raise ValueError("The certificate was encrypted with a password but no password was provided.")
|
||||
|
||||
if password is not None and serialized_certificate_info.endswith(_NO_PASSWORD_USED_BYTES):
|
||||
raise ValueError("The certificate was not encrypted with a password but a password was provided.")
|
||||
|
||||
serialized_certificate_info = serialized_certificate_info[0:-2]
|
||||
|
||||
if not password:
|
||||
private_key = Transaction.output_class.private_key_from_str(serialized_certificate_info)
|
||||
else:
|
||||
decrypted_private_key = self.decrypt_serilized_info_with_password(serialized_certificate_info, password)
|
||||
private_key = Transaction.output_class.private_key_from_str(decrypted_private_key)
|
||||
|
||||
public_key_bytes = private_key.get_verifying_key().to_der()
|
||||
channel_pubkey_hash = account.ledger.public_key_to_address(public_key_bytes)
|
||||
account.channel_keys[channel_pubkey_hash] = private_key.to_pem().decode()
|
||||
account.wallet.save()
|
||||
|
||||
def encrypt_private_key_with_password(self, private_key_str, password):
|
||||
pass
|
||||
|
||||
def save(self):
|
||||
for wallet in self.wallets:
|
||||
wallet.save()
|
||||
|
|
|
@ -159,6 +159,14 @@ class Output(BaseOutput):
|
|||
def is_channel_private_key(self, private_key):
|
||||
return self.claim.channel.public_key_bytes == private_key.get_verifying_key().to_der()
|
||||
|
||||
@staticmethod
|
||||
def pem_to_private_key_str(private_key_pem):
|
||||
return ecdsa.SigningKey.from_pem(private_key_pem, hashfunc=hashlib.sha256).to_string()
|
||||
|
||||
@staticmethod
|
||||
def private_key_from_str(private_key_str):
|
||||
return ecdsa.SigningKey.from_string(private_key_str, curve=ecdsa.SECP256k1, hashfunc=hashlib.sha256)
|
||||
|
||||
@classmethod
|
||||
def pay_claim_name_pubkey_hash(
|
||||
cls, amount: int, claim_name: str, claim: Claim, pubkey_hash: bytes) -> 'Output':
|
||||
|
|
|
@ -306,6 +306,32 @@ class ChannelCommands(CommandTestCase):
|
|||
txo = (await account2.get_channels())[0]
|
||||
self.assertIsNotNone(txo.private_key)
|
||||
|
||||
async def test_channel_export_import_without_password(self):
|
||||
tx = await self.channel_create('@foo', '1.0')
|
||||
claim_id = tx['outputs'][0]['claim_id']
|
||||
channel_private_key = (await self.account.get_channels())[0].private_key
|
||||
|
||||
_account2 = await self.out(self.daemon.jsonrpc_account_create("Account 2"))
|
||||
account2_id, account2 = _account2["id"], self.daemon.get_account_or_error(_account2['id'])
|
||||
|
||||
# before exporting/importing channel
|
||||
self.assertEqual(len(await self.daemon.jsonrpc_channel_list(account_id=account2_id)), 0)
|
||||
|
||||
# exporting from default account
|
||||
serialized_channel_info = await self.out(self.daemon.jsonrpc_channel_export(claim_id, insecure=True))
|
||||
|
||||
other_address = await account2.receiving.get_or_create_usable_address()
|
||||
await self.out(self.channel_update(claim_id, claim_address=other_address))
|
||||
|
||||
# importing into second account
|
||||
await self.daemon.jsonrpc_channel_import(serialized_channel_info, password=None, account_id=account2_id)
|
||||
|
||||
# after exporting/importing channel
|
||||
self.assertEqual(len(await self.daemon.jsonrpc_channel_list(account_id=account2_id)), 1)
|
||||
txo_channel_account2 = (await account2.get_channels())[0]
|
||||
|
||||
self.assertEqual(channel_private_key, txo_channel_account2.private_key)
|
||||
|
||||
|
||||
class StreamCommands(CommandTestCase):
|
||||
|
||||
|
|
Loading…
Reference in a new issue