forked from LBRYCommunity/lbry-sdk
channel import/export wip
This commit is contained in:
parent
ad411cc157
commit
0156aa8b00
4 changed files with 50 additions and 82 deletions
|
@ -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.
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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':
|
||||||
|
|
|
@ -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))
|
||||||
|
|
Loading…
Reference in a new issue