channel import/export wip

This commit is contained in:
Lex Berezhny 2019-05-29 01:21:54 -04:00
parent ad411cc157
commit 0156aa8b00
4 changed files with 50 additions and 82 deletions

View file

@ -7,6 +7,8 @@ import inspect
import typing import typing
import base58 import base58
import random import random
import ecdsa
import hashlib
from urllib.parse import urlencode, quote from urllib.parse import urlencode, quote
from typing import Callable, Optional, List from typing import Callable, Optional, List
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
@ -2122,40 +2124,69 @@ class Daemon(metaclass=JSONRPCServerType):
) )
@requires(WALLET_COMPONENT) @requires(WALLET_COMPONENT)
async def jsonrpc_channel_export(self, claim_id, password=None, account_id=None): async def jsonrpc_channel_export(self, channel_id=None, channel_name=None, account_id=None):
""" """
Export serialized channel signing information for a given certificate claim id Export channel private key.
Usage: Usage:
channel_export (<claim_id> | --claim_id=<claim_id>) channel_export (<channel_id> | --channel_id=<channel_id> | --channel_name=<channel_name>)
[--account_id=<account_id>...]
Options: Options:
--claim_id=<claim_id> : (str) Claim ID to export information about --channel_id=<channel_id> : (str) claim id of channel to export
--channel_name=<channel_name> : (str) name of channel to export
--account_id=<account_id> : (str) one or more account ids for accounts
to look in for channels, defaults to
all accounts.
Returns: Returns:
(str) Serialized certificate information (str) serialized channel private key
""" """
account = self.get_account_or_default(account_id) channel = await self.get_channel_or_error(account_id, channel_id, channel_name, for_signing=True)
address = channel.get_address(self.ledger)
return await self.wallet_manager.export_certificate_info(claim_id, account, password) public_key = await self.ledger.get_public_key_for_address(channel.get_address(self.ledger))
if not public_key:
raise Exception("Can't find public key for address holding the channel.")
export = {
'name': channel.claim_name,
'channel_id': channel.claim_id,
'holding_address': address,
'holding_public_key': public_key.extended_key_string(),
'signing_private_key': channel.private_key.to_pem().decode()
}
return base58.b58encode(json.dumps(export, separators=(',', ':')))
@requires(WALLET_COMPONENT) @requires(WALLET_COMPONENT)
async def jsonrpc_channel_import(self, serialized_certificate_info, password=None, account_id=None): async def jsonrpc_channel_import(self, channel_data):
""" """
Import serialized channel signing information (to allow signing new claims to the channel) Import serialized channel private key (to allow signing new streams to the channel)
Usage: Usage:
channel_import (<serialized_certificate_info> | --serialized_certificate_info=<serialized_certificate_info>) channel_import (<channel_data> | --channel_data=<channel_data>)
Options: Options:
--serialized_certificate_info=<serialized_certificate_info> : (str) certificate info --channel_data=<channel_data> : (str) serialized channel, as exported by channel export
Returns: Returns:
(dict) Result dictionary (dict) Result dictionary
""" """
account = self.get_account_or_default(account_id) decoded = base58.b58decode(channel_data)
data = json.loads(decoded)
return await self.wallet_manager.import_certificate_info(serialized_certificate_info, password, account) channel_private_key = ecdsa.SigningKey.from_pem(
data['signing_private_key'], hashfunc=hashlib.sha256
)
account: LBCAccount = await self.ledger.get_account_for_address(data['holding_address'])
if not account:
new_account = LBCAccount.from_dict(self.ledger, self.default_wallet, {
'name': f"Holding Account For Channel {data['name']}",
'public_key': data['holding_public_key'],
'address_generator': {'name': 'single-address'}
})
if self.ledger.network.is_connected:
asyncio.create_task(self.ledger.subscribe_account(new_account))
account.add_channel_private_key(channel_private_key)
self.default_wallet.save()
return f"Added channel signing key for {data['name']}."
STREAM_DOC = """ STREAM_DOC = """
Create, update, abandon, list and inspect your stream claims. Create, update, abandon, list and inspect your stream claims.

View file

@ -1,7 +1,7 @@
import os import os
import json import json
import logging import logging
from binascii import unhexlify, hexlify from binascii import unhexlify
from datetime import datetime from datetime import datetime
@ -17,9 +17,6 @@ from lbrynet.wallet.dewies import dewies_to_lbc
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
_NO_PASSWORD_USED_BYTES = b'00'
_PASSWORD_USED_BYTES = b'01'
class LbryWalletManager(BaseWalletManager): class LbryWalletManager(BaseWalletManager):
@ -304,58 +301,6 @@ class LbryWalletManager(BaseWalletManager):
history.append(item) history.append(item)
return history 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): def save(self):
for wallet in self.wallets: for wallet in self.wallets:
wallet.save() wallet.save()

View file

@ -159,14 +159,6 @@ class Output(BaseOutput):
def is_channel_private_key(self, private_key): def is_channel_private_key(self, private_key):
return self.claim.channel.public_key_bytes == private_key.get_verifying_key().to_der() 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 @classmethod
def pay_claim_name_pubkey_hash( def pay_claim_name_pubkey_hash(
cls, amount: int, claim_name: str, claim: Claim, pubkey_hash: bytes) -> 'Output': cls, amount: int, claim_name: str, claim: Claim, pubkey_hash: bytes) -> 'Output':

View file

@ -299,8 +299,8 @@ class ChannelCommands(CommandTestCase):
self.assertIsNone(txo.private_key) self.assertIsNone(txo.private_key)
# send the private key too # send the private key too
channel_public_key = self.account.get_channel_private_key(unhexlify(channel['public_key'])) private_key = self.account.get_channel_private_key(unhexlify(channel['public_key']))
account2.add_channel_private_key(channel_public_key) account2.add_channel_private_key(private_key)
# now should have private key # now should have private key
txo = (await account2.get_channels())[0] txo = (await account2.get_channels())[0]
@ -318,7 +318,7 @@ class ChannelCommands(CommandTestCase):
self.assertEqual(len(await self.daemon.jsonrpc_channel_list(account_id=account2_id)), 0) self.assertEqual(len(await self.daemon.jsonrpc_channel_list(account_id=account2_id)), 0)
# exporting from default account # exporting from default account
serialized_channel_info = await self.out(self.daemon.jsonrpc_channel_export(claim_id, insecure=True)) serialized_channel_info = await self.out(self.daemon.jsonrpc_channel_export(claim_id))
other_address = await account2.receiving.get_or_create_usable_address() other_address = await account2.receiving.get_or_create_usable_address()
await self.out(self.channel_update(claim_id, claim_address=other_address)) await self.out(self.channel_update(claim_id, claim_address=other_address))