Merge pull request #3129 from lbryio/signing_api
add `channel_sign` api for signing data with a channel identity
This commit is contained in:
commit
652773d2cf
3 changed files with 67 additions and 22 deletions
|
@ -26,42 +26,45 @@ def is_comment_signed_by_channel(comment: dict, channel: Output, sign_comment_id
|
||||||
if isinstance(channel, Output):
|
if isinstance(channel, Output):
|
||||||
try:
|
try:
|
||||||
signing_field = comment['comment_id'] if sign_comment_id else comment['comment']
|
signing_field = comment['comment_id'] if sign_comment_id else comment['comment']
|
||||||
pieces = [
|
return verify(channel, signing_field.encode(), comment, cid2hash(comment['channel_id']))
|
||||||
comment['signing_ts'].encode(),
|
|
||||||
cid2hash(comment['channel_id']),
|
|
||||||
signing_field.encode()
|
|
||||||
]
|
|
||||||
return Output.is_signature_valid(
|
|
||||||
get_encoded_signature(comment['signature']),
|
|
||||||
sha256(b''.join(pieces)),
|
|
||||||
channel.claim.channel.public_key_bytes
|
|
||||||
)
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def verify(channel, data, signature, channel_hash=None):
|
||||||
|
pieces = [
|
||||||
|
signature['signing_ts'].encode(),
|
||||||
|
channel_hash or channel.claim_hash,
|
||||||
|
data
|
||||||
|
]
|
||||||
|
return Output.is_signature_valid(
|
||||||
|
get_encoded_signature(signature['signature']),
|
||||||
|
sha256(b''.join(pieces)),
|
||||||
|
channel.claim.channel.public_key_bytes
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def sign_comment(comment: dict, channel: Output, sign_comment_id=False):
|
def sign_comment(comment: dict, channel: Output, sign_comment_id=False):
|
||||||
timestamp = str(int(time.time()))
|
|
||||||
signing_field = comment['comment_id'] if sign_comment_id else comment['comment']
|
signing_field = comment['comment_id'] if sign_comment_id else comment['comment']
|
||||||
pieces = [timestamp.encode(), channel.claim_hash, signing_field.encode()]
|
comment.update(sign(channel, signing_field.encode()))
|
||||||
|
|
||||||
|
|
||||||
|
def sign(channel, data):
|
||||||
|
timestamp = str(int(time.time()))
|
||||||
|
pieces = [timestamp.encode(), channel.claim_hash, data]
|
||||||
digest = sha256(b''.join(pieces))
|
digest = sha256(b''.join(pieces))
|
||||||
signature = channel.private_key.sign_digest_deterministic(digest, hashfunc=hashlib.sha256)
|
signature = channel.private_key.sign_digest_deterministic(digest, hashfunc=hashlib.sha256)
|
||||||
comment.update({
|
return {
|
||||||
'signature': binascii.hexlify(signature).decode(),
|
'signature': binascii.hexlify(signature).decode(),
|
||||||
'signing_ts': timestamp
|
'signing_ts': timestamp
|
||||||
})
|
}
|
||||||
|
|
||||||
|
|
||||||
def sign_reaction(reaction: dict, channel: Output):
|
def sign_reaction(reaction: dict, channel: Output):
|
||||||
timestamp = str(int(time.time()))
|
|
||||||
signing_field = reaction['channel_name']
|
signing_field = reaction['channel_name']
|
||||||
pieces = [timestamp.encode(), channel.claim_hash, signing_field.encode()]
|
reaction.update(sign(channel, signing_field.encode()))
|
||||||
digest = sha256(b''.join(pieces))
|
|
||||||
signature = channel.private_key.sign_digest_deterministic(digest, hashfunc=hashlib.sha256)
|
|
||||||
reaction.update({
|
|
||||||
'signature': binascii.hexlify(signature).decode(),
|
|
||||||
'signing_ts': timestamp
|
|
||||||
})
|
|
||||||
|
|
||||||
async def jsonrpc_post(url: str, method: str, params: dict = None, **kwargs) -> any:
|
async def jsonrpc_post(url: str, method: str, params: dict = None, **kwargs) -> any:
|
||||||
params = params or {}
|
params = params or {}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import base58
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from prometheus_client import generate_latest as prom_generate_latest, Gauge, Histogram, Counter
|
from prometheus_client import generate_latest as prom_generate_latest, Gauge, Histogram, Counter
|
||||||
from google.protobuf.message import DecodeError
|
from google.protobuf.message import DecodeError
|
||||||
|
|
||||||
from lbry.wallet import (
|
from lbry.wallet import (
|
||||||
Wallet, ENCRYPT_ON_DISK, SingleKey, HierarchicalDeterministic,
|
Wallet, ENCRYPT_ON_DISK, SingleKey, HierarchicalDeterministic,
|
||||||
Transaction, Output, Input, Account, database
|
Transaction, Output, Input, Account, database
|
||||||
|
@ -2788,6 +2789,34 @@ class Daemon(metaclass=JSONRPCServerType):
|
||||||
|
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
|
@requires(WALLET_COMPONENT)
|
||||||
|
async def jsonrpc_channel_sign(
|
||||||
|
self, channel_name=None, channel_id=None, hexdata=None, channel_account_id=None, wallet_id=None):
|
||||||
|
"""
|
||||||
|
Signs data using the specified channel signing key.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
channel_sign [<channel_name> | --channel_name=<channel_name>]
|
||||||
|
[<channel_id> | --channel_id=<channel_id>] [<hexdata> | --hexdata=<hexdata>]
|
||||||
|
[--channel_account_id=<channel_account_id>...] [--wallet_id=<wallet_id>]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--channel_name=<channel_name> : (str) name of channel used to sign (or use channel id)
|
||||||
|
--channel_id=<channel_id> : (str) claim id of channel used to sign (or use channel name)
|
||||||
|
--hexdata=<hexdata> : (str) data to sign, encoded as hexadecimal
|
||||||
|
--channel_account_id=<channel_account_id>: (str) one or more account ids for accounts to look in
|
||||||
|
for channel certificates, defaults to all accounts.
|
||||||
|
--wallet_id=<wallet_id> : (str) restrict operation to specific wallet
|
||||||
|
|
||||||
|
Returns: {}
|
||||||
|
"""
|
||||||
|
wallet = self.wallet_manager.get_wallet_or_default(wallet_id)
|
||||||
|
assert not wallet.is_locked, "Cannot spend funds with locked wallet, unlock first."
|
||||||
|
signing_channel = await self.get_channel_or_error(
|
||||||
|
wallet, channel_account_id, channel_id, channel_name, for_signing=True
|
||||||
|
)
|
||||||
|
return comment_client.sign(signing_channel, unhexlify(hexdata))
|
||||||
|
|
||||||
@requires(WALLET_COMPONENT)
|
@requires(WALLET_COMPONENT)
|
||||||
async def jsonrpc_channel_abandon(
|
async def jsonrpc_channel_abandon(
|
||||||
self, claim_id=None, txid=None, nout=None, account_id=None, wallet_id=None,
|
self, claim_id=None, txid=None, nout=None, account_id=None, wallet_id=None,
|
||||||
|
|
|
@ -6,6 +6,7 @@ from binascii import unhexlify
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
from lbry.error import InsufficientFundsError
|
from lbry.error import InsufficientFundsError
|
||||||
|
from lbry.extras.daemon.comment_client import verify
|
||||||
|
|
||||||
from lbry.extras.daemon.daemon import DEFAULT_PAGE_SIZE
|
from lbry.extras.daemon.daemon import DEFAULT_PAGE_SIZE
|
||||||
from lbry.testcase import CommandTestCase
|
from lbry.testcase import CommandTestCase
|
||||||
|
@ -1004,6 +1005,18 @@ class ChannelCommands(CommandTestCase):
|
||||||
self.assertItemCount(await self.daemon.jsonrpc_channel_list(account_id=self.account.id), 2)
|
self.assertItemCount(await self.daemon.jsonrpc_channel_list(account_id=self.account.id), 2)
|
||||||
self.assertItemCount(await self.daemon.jsonrpc_channel_list(account_id=account2_id), 1)
|
self.assertItemCount(await self.daemon.jsonrpc_channel_list(account_id=account2_id), 1)
|
||||||
|
|
||||||
|
async def test_sign_hex_encoded_data(self):
|
||||||
|
data_to_sign = "CAFEBABE"
|
||||||
|
# claim new name
|
||||||
|
await self.channel_create('@someotherchan')
|
||||||
|
channel_tx = await self.daemon.jsonrpc_channel_create('@signer', '0.1')
|
||||||
|
await self.confirm_tx(channel_tx.id)
|
||||||
|
channel = channel_tx.outputs[0]
|
||||||
|
signature1 = await self.out(self.daemon.jsonrpc_channel_sign(channel_name='@signer', hexdata=data_to_sign))
|
||||||
|
signature2 = await self.out(self.daemon.jsonrpc_channel_sign(channel_id=channel.claim_id, hexdata=data_to_sign))
|
||||||
|
self.assertTrue(verify(channel, unhexlify(data_to_sign), signature1))
|
||||||
|
self.assertTrue(verify(channel, unhexlify(data_to_sign), signature2))
|
||||||
|
|
||||||
async def test_channel_export_import_before_sending_channel(self):
|
async def test_channel_export_import_before_sending_channel(self):
|
||||||
# export
|
# export
|
||||||
tx = await self.channel_create('@foo', '1.0')
|
tx = await self.channel_create('@foo', '1.0')
|
||||||
|
|
Loading…
Reference in a new issue