From 879960dcd14858e0460d314e7daf56bcfb1b6d16 Mon Sep 17 00:00:00 2001 From: Andrew Redden Date: Sat, 31 Aug 2019 13:38:24 +0200 Subject: [PATCH 1/3] Celery Task Scheulder and Worker Setup - dashboard/tasks.py - bounty_email task - locks on the invite_url - django_celery_beat depdency added - celery dependency RedisService added Settings.py - celery specific env variables added docker-compose.yml - worker and scheduler added --- app/app/redis_service.py | 13 +++++ app/app/settings.py | 43 +++++++++++------ app/dashboard/tasks.py | 50 +++++++++++++++++++ app/marketing/mails.py | 96 +++++++++++++++++-------------------- app/taskapp/__init__.py | 0 app/taskapp/celery.py | 27 +++++++++++ bin/celery/scheduler/run.sh | 13 +++++ bin/celery/worker/run.sh | 5 ++ docker-compose.yml | 22 +++++++++ requirements/base.txt | 2 + 10 files changed, 204 insertions(+), 67 deletions(-) create mode 100644 app/app/redis_service.py create mode 100644 app/dashboard/tasks.py create mode 100644 app/taskapp/__init__.py create mode 100644 app/taskapp/celery.py create mode 100644 bin/celery/scheduler/run.sh create mode 100644 bin/celery/worker/run.sh diff --git a/app/app/redis_service.py b/app/app/redis_service.py new file mode 100644 index 00000000000..ff8556aafbf --- /dev/null +++ b/app/app/redis_service.py @@ -0,0 +1,13 @@ +from django.conf import settings +from redis import Redis + + +class RedisService: + def __new__(cls): + if not hasattr(cls, 'instance'): + cls.instance = super().__new__(cls) + return cls.instance + + def __init__(self): + redis_url = settings.CELERY_BROKER_URL + self.redis = Redis.from_url(redis_url) diff --git a/app/app/settings.py b/app/app/settings.py index f5eb52aa893..c77e7dd087f 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -31,6 +31,7 @@ from easy_thumbnails.conf import Settings as easy_thumbnails_defaults import warnings + warnings.filterwarnings("ignore", category=UserWarning, module='psycopg2') root = environ.Path(__file__) - 2 # Set the base directory to two levels. @@ -62,6 +63,8 @@ INSTALLED_APPS = [ 'corsheaders', 'django.contrib.admin', + 'taskapp.celery.CeleryConfig', + 'django_celery_beat', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', @@ -176,8 +179,8 @@ REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. - 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend', ), - 'DEFAULT_THROTTLE_CLASSES': ('rest_framework.throttling.AnonRateThrottle', ), + 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',), + 'DEFAULT_THROTTLE_CLASSES': ('rest_framework.throttling.AnonRateThrottle',), 'DEFAULT_THROTTLE_RATES': { 'anon': '1000/day', }, @@ -197,7 +200,7 @@ USE_TZ = env.bool('USE_TZ', default=True) TIME_ZONE = env.str('TIME_ZONE', default='UTC') -LOCALE_PATHS = ('locale', ) +LOCALE_PATHS = ('locale',) LANGUAGES = [ ('en', gettext_noop('English')), @@ -250,7 +253,6 @@ if RELEASE: RAVEN_CONFIG['release'] = RELEASE - LOGGING = { 'version': 1, 'disable_existing_loggers': False, @@ -309,13 +311,13 @@ 'format': '%(hostname)s %(name)-12s [%(levelname)-8s] %(message)s', } LOGGING['handlers']['watchtower'] = { - 'level': AWS_LOG_LEVEL, - 'class': 'watchtower.django.DjangoCloudWatchLogHandler', - 'boto3_session': boto3_session, - 'log_group': AWS_LOG_GROUP, - 'stream_name': AWS_LOG_STREAM, - 'filters': ['host_filter'], - 'formatter': 'cloudwatch', + 'level': AWS_LOG_LEVEL, + 'class': 'watchtower.django.DjangoCloudWatchLogHandler', + 'boto3_session': boto3_session, + 'log_group': AWS_LOG_GROUP, + 'stream_name': AWS_LOG_STREAM, + 'filters': ['host_filter'], + 'formatter': 'cloudwatch', } LOGGING['loggers']['django.db.backends']['level'] = AWS_LOG_LEVEL @@ -341,7 +343,7 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ -STATICFILES_DIRS = env.tuple('STATICFILES_DIRS', default=('assets/', )) +STATICFILES_DIRS = env.tuple('STATICFILES_DIRS', default=('assets/',)) STATIC_ROOT = root('static') STATICFILES_LOCATION = env.str('STATICFILES_LOCATION', default='static') MEDIAFILES_LOCATION = env.str('MEDIAFILES_LOCATION', default='media') @@ -366,7 +368,7 @@ COMPRESS_ROOT = STATIC_ROOT COMPRESS_ENABLED = env.bool('COMPRESS_ENABLED', default=True) -THUMBNAIL_PROCESSORS = easy_thumbnails_defaults.THUMBNAIL_PROCESSORS + ('app.thumbnail_processors.circular_processor', ) +THUMBNAIL_PROCESSORS = easy_thumbnails_defaults.THUMBNAIL_PROCESSORS + ('app.thumbnail_processors.circular_processor',) THUMBNAIL_ALIASES = { '': { @@ -384,6 +386,7 @@ CACHEOPS_DEGRADE_ON_FAILURE = env.bool('CACHEOPS_DEGRADE_ON_FAILURE', default=True) CACHEOPS_REDIS = env.str('CACHEOPS_REDIS', default='redis://redis:6379/0') + CACHEOPS_DEFAULTS = { 'timeout': 60 * 60 } @@ -447,6 +450,16 @@ }, } + +# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-broker_url +CELERY_BROKER_URL = env('CELERY_BROKER_URL', default=CACHEOPS_REDIS) +# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-accept_content +CELERY_ACCEPT_CONTENT = ['json'] +# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-task_serializer +CELERY_TASK_SERIALIZER = 'json' +# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-result_serializer +CELERY_RESULT_SERIALIZER = 'json' + DJANGO_REDIS_IGNORE_EXCEPTIONS = env.bool('REDIS_IGNORE_EXCEPTIONS', default=True) DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = env.bool('REDIS_LOG_IGNORED_EXCEPTIONS', default=True) COLLECTFAST_CACHE = env('COLLECTFAST_CACHE', default='collectfast') @@ -637,8 +650,8 @@ AWS_S3_OBJECT_PARAMETERS = {'CacheControl': f'max-age={AWS_S3_CACHE_MAX_AGE}', } CORS_ORIGIN_ALLOW_ALL = False -CORS_ORIGIN_WHITELIST = ('sumo.com', 'load.sumo.com', 'googleads.g.doubleclick.net', 'gitcoin.co', 'github.com', ) -CORS_ORIGIN_WHITELIST = CORS_ORIGIN_WHITELIST + (AWS_S3_CUSTOM_DOMAIN, MEDIA_CUSTOM_DOMAIN, ) +CORS_ORIGIN_WHITELIST = ('sumo.com', 'load.sumo.com', 'googleads.g.doubleclick.net', 'gitcoin.co', 'github.com',) +CORS_ORIGIN_WHITELIST = CORS_ORIGIN_WHITELIST + (AWS_S3_CUSTOM_DOMAIN, MEDIA_CUSTOM_DOMAIN,) S3_REPORT_BUCKET = env('S3_REPORT_BUCKET', default='') # TODO S3_REPORT_PREFIX = env('S3_REPORT_PREFIX', default='') # TODO diff --git a/app/dashboard/tasks.py b/app/dashboard/tasks.py new file mode 100644 index 00000000000..4aa28ae70a0 --- /dev/null +++ b/app/dashboard/tasks.py @@ -0,0 +1,50 @@ +from django.conf import settings + +from celery import app +from celery.utils.log import get_task_logger +from app.redis_service import RedisService +from dashboard.models import Profile +from marketing.mails import send_mail, func_name +from retail.emails import render_share_bounty + +logger = get_task_logger(__name__) + +redis = RedisService().redis + +# Lock timeout of 2 minutes (just in the case that the application hangs to avoid a redis deadlock) +LOCK_TIMEOUT = 60 * 2 + + +@app.shared_task(bind=True, max_retries=3) +def bounty_emails(self, emails, msg, profile_handle, invite_url=None, kudos_invite=False, retry: bool = True) -> None: + """ + :param self: + :param emails: + :param msg: + :param profile_handle: + :param invite_url: + :param kudos_invite: + :return: + """ + with redis.lock("tasks:bounty_email:%s" % invite_url, timeout=LOCK_TIMEOUT): + # need to look at how to send bulk emails with SG + profile = Profile.objects.get(handle=profile_handle) + try: + for email in emails: + to_email = email + from_email = settings.CONTACT_EMAIL + subject = "You have been invited to work on a bounty." + html, text = render_share_bounty(to_email, msg, profile, invite_url, kudos_invite) + send_mail( + from_email, + to_email, + subject, + text, + html, + from_name=f"@{profile.handle}", + categories=['transactional', func_name()], + ) + + except ConnectionError as exc: + print(exc) + self.retry(30) diff --git a/app/marketing/mails.py b/app/marketing/mails.py index 40686128634..2f3cff24ac1 100644 --- a/app/marketing/mails.py +++ b/app/marketing/mails.py @@ -102,13 +102,15 @@ def send_mail(from_email, _to_email, subject, body, html=False, try: response = sg.client.mail.send.post(request_body=mail.get()) except UnauthorizedError as e: - logger.debug(f'-- Sendgrid Mail failure - {_to_email} / {categories} - Unauthorized - Check sendgrid credentials') + logger.debug( + f'-- Sendgrid Mail failure - {_to_email} / {categories} - Unauthorized - Check sendgrid credentials') logger.debug(e) except HTTPError as e: logger.debug(f'-- Sendgrid Mail failure - {_to_email} / {categories} - {e}') return response + def nth_day_email_campaign(nth, subscriber): firstname = subscriber.email.split('@')[0] @@ -369,7 +371,8 @@ def tip_email(tip, to_emails, is_new): warning = '' if tip.network == 'mainnet' else "({})".format(tip.network) subject = gettext("⚡️ New Tip Worth {} {} {}").format(round(tip.amount, round_decimals), warning, tip.tokenName) if not is_new: - subject = gettext("🕐 Tip Worth {} {} {} Expiring Soon").format(round(tip.amount, round_decimals), warning, tip.tokenName) + subject = gettext("🕐 Tip Worth {} {} {} Expiring Soon").format(round(tip.amount, round_decimals), warning, + tip.tokenName) for to_email in to_emails: cur_language = translation.get_language() @@ -436,7 +439,7 @@ def warn_account_out_of_eth(account, balance, denomination): setup_lang(to_email) subject = account + str(_(" is out of gas")) body_str = _("is down to ") - body = f"{account } {body_str} {balance} {denomination}" + body = f"{account} {body_str} {balance} {denomination}" if not should_suppress_notification_email(to_email, 'admin'): send_mail( from_email, @@ -457,7 +460,7 @@ def warn_subscription_failed(subscription): try: setup_lang(to_email) subject = str(subscription.pk) + str(_(" subscription failed")) - body = f"{settings.BASE_URL}{subscription.admin_url }\n{subscription.contributor_profile.email}, {subscription.contributor_profile.user.email}
\n\n{subscription.subminer_comments}
" + body = f"{settings.BASE_URL}{subscription.admin_url}\n{subscription.contributor_profile.email}, {subscription.contributor_profile.user.email}
\n\n{subscription.subminer_comments}
" if not should_suppress_notification_email(to_email, 'admin'): send_mail( from_email, @@ -471,7 +474,6 @@ def warn_subscription_failed(subscription): translation.activate(cur_language) - def new_feedback(email, feedback): to_email = 'product@gitcoin.co' from_email = settings.SERVER_EMAIL @@ -528,20 +530,8 @@ def funder_payout_reminder(to_email, bounty, github_username, live): def share_bounty(emails, msg, profile, invite_url=None, kudos_invite=False): - for email in emails: - to_email = email - from_email = settings.CONTACT_EMAIL - subject = "You have been invited to work on a bounty." - html, text = render_share_bounty(to_email, msg, profile, invite_url, kudos_invite) - send_mail( - from_email, - to_email, - subject, - text, - html, - from_name=f"@{profile.handle}", - categories=['transactional', func_name()], - ) + from dashboard.tasks import bounty_emails + bounty_emails.delay(emails, msg, profile.handle, invite_url, kudos_invite) def new_reserved_issue(from_email, user, bounty): @@ -585,6 +575,7 @@ def reject_faucet_request(fr): finally: translation.activate(cur_language) + def new_bounty_daily(bounties, old_bounties, to_emails=None): if not bounties: return @@ -621,7 +612,7 @@ def weekly_roundup(to_emails=None): setup_lang(to_email) html, text, subject = render_new_bounty_roundup(to_email) from_email = settings.PERSONAL_CONTACT_EMAIL - + if not html: print("no content") return @@ -668,10 +659,11 @@ def weekly_recap(to_emails=None): finally: translation.activate(cur_language) + def unread_notification_email_weekly_roundup(to_emails=None): if to_emails is None: to_emails = [] - + cur_language = translation.get_language() for to_email in to_emails: try: @@ -681,12 +673,12 @@ def unread_notification_email_weekly_roundup(to_emails=None): if not should_suppress_notification_email(to_email, 'weeklyrecap'): send_mail( - from_email, - to_email, - subject, - text, - html, - from_name="Kevin Owocki (Gitcoin.co)", + from_email, + to_email, + subject, + text, + html, + from_name="Kevin Owocki (Gitcoin.co)", categories=['marketing', func_name()], ) else: @@ -813,7 +805,6 @@ def bounty_changed(bounty, to_emails=None): def new_match(to_emails, bounty, github_username): - subject = gettext("⚡️ {} Meet {}: {}! ").format(github_username.title(), bounty.org_name.title(), bounty.title) to_email = to_emails[0] @@ -886,7 +877,8 @@ def bounty_expire_warning(bounty, to_emails=None): unit = _('hour') num = int(round((bounty.expires_date - timezone.now()).seconds / 3600 / 24, 0)) unit = unit + ("s" if num != 1 else "") - subject = gettext("😕 Your Funded Issue ({}) Expires In {} {} ... 😕").format(bounty.title_or_desc, num, unit) + subject = gettext("😕 Your Funded Issue ({}) Expires In {} {} ... 😕").format(bounty.title_or_desc, num, + unit) from_email = settings.CONTACT_EMAIL html, text = render_bounty_expire_warning(to_email, bounty) @@ -1077,7 +1069,7 @@ def new_bounty_request(model): setup_lang(to_email) subject = _("New Bounty Request") body_str = _("New Bounty Request from") - body = f"{body_str} {model.requested_by}: "\ + body = f"{body_str} {model.requested_by}: " \ f"{settings.BASE_URL}_administrationbounty_requests/bountyrequest/{model.pk}/change" send_mail( from_email, @@ -1098,21 +1090,21 @@ def new_funding_limit_increase_request(profile, cleaned_data): usdt_per_tx = cleaned_data.get('usdt_per_tx', 0) usdt_per_week = cleaned_data.get('usdt_per_week', 0) comment = cleaned_data.get('comment', '') - accept_link = f'{settings.BASE_URL}requestincrease?'\ - f'profile_pk={profile.pk}&'\ - f'usdt_per_tx={usdt_per_tx}&'\ - f'usdt_per_week={usdt_per_week}' + accept_link = f'{settings.BASE_URL}requestincrease?' \ + f'profile_pk={profile.pk}&' \ + f'usdt_per_tx={usdt_per_tx}&' \ + f'usdt_per_week={usdt_per_week}' try: setup_lang(to_email) subject = _('New Funding Limit Increase Request') - body = f'New Funding Limit Request from {profile} ({profile.absolute_url}).\n\n'\ - f'New Limit in USD per Transaction: {usdt_per_tx}\n'\ - f'New Limit in USD per Week: {usdt_per_week}\n\n'\ - f'To accept the Funding Limit, visit: {accept_link}\n'\ - f'Administration Link: ({settings.BASE_URL}_administrationdashboard/profile/'\ - f'{profile.pk}/change/#id_max_tip_amount_usdt_per_tx)\n\n'\ - f'Comment:\n{comment}' + body = f'New Funding Limit Request from {profile} ({profile.absolute_url}).\n\n' \ + f'New Limit in USD per Transaction: {usdt_per_tx}\n' \ + f'New Limit in USD per Week: {usdt_per_week}\n\n' \ + f'To accept the Funding Limit, visit: {accept_link}\n' \ + f'Administration Link: ({settings.BASE_URL}_administrationdashboard/profile/' \ + f'{profile.pk}/change/#id_max_tip_amount_usdt_per_tx)\n\n' \ + f'Comment:\n{comment}' send_mail(from_email, to_email, subject, body, from_name=_("No Reply from Gitcoin.co")) finally: @@ -1132,17 +1124,17 @@ def bounty_request_feedback(profile): try: setup_lang(to_email) subject = _(f'Bounty Request Feedback, @{profile.username} <> Gitcoin') - body = f'Howdy @{profile.username},\n\n'\ - 'This is Vivek from Gitcoin. '\ - 'I noticed you made a funded Gitcoin Requests '\ - 'a few months ago and just wanted to check in. '\ - 'How\'d it go? Any feedback for us?\n\n'\ - 'Let us know if you have any bounties in your near future '\ - '-- we\'ll pay attention to '\ - 'Gitcoin Requests (https://gitcoin.co/requests/) '\ - 'from you as we know you\'ve suggested good things '\ - 'in the past 🙂\n\n'\ - 'Best,\n\nV' + body = f'Howdy @{profile.username},\n\n' \ + 'This is Vivek from Gitcoin. ' \ + 'I noticed you made a funded Gitcoin Requests ' \ + 'a few months ago and just wanted to check in. ' \ + 'How\'d it go? Any feedback for us?\n\n' \ + 'Let us know if you have any bounties in your near future ' \ + '-- we\'ll pay attention to ' \ + 'Gitcoin Requests (https://gitcoin.co/requests/) ' \ + 'from you as we know you\'ve suggested good things ' \ + 'in the past 🙂\n\n' \ + 'Best,\n\nV' send_mail( from_email, diff --git a/app/taskapp/__init__.py b/app/taskapp/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/taskapp/celery.py b/app/taskapp/celery.py new file mode 100644 index 00000000000..f0ddd3ecaed --- /dev/null +++ b/app/taskapp/celery.py @@ -0,0 +1,27 @@ +import os + +from celery import Celery +from celery.signals import setup_logging +from django.apps import AppConfig, apps + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + +app = Celery('app') + + +class CeleryConfig(AppConfig): + name = 'taskapp' + verbose_name = 'Celery Config' + + # Use Django logging instead of celery logger + @setup_logging.connect + def on_celery_setup_logging(**kwargs): + pass + + def ready(self): + # Using a string here means the worker will not have to + # pickle the object when using Windows. + app.config_from_object('django.conf:settings', namespace='CELERY') + installed_apps = [app_config.name for app_config in apps.get_app_configs()] + print(installed_apps) + app.autodiscover_tasks(lambda: installed_apps, force=True) diff --git a/bin/celery/scheduler/run.sh b/bin/celery/scheduler/run.sh new file mode 100644 index 00000000000..272a88d7397 --- /dev/null +++ b/bin/celery/scheduler/run.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -euo pipefail + +# DEBUG set in .env +if [ ${DEBUG:-0} = 1 ]; then + log_level="debug" +else + log_level="info" +fi + +echo "==> Running Celery beat <==" +exec celery beat -A taskapp -S django_celery_beat.schedulers:DatabaseScheduler --loglevel $log_level diff --git a/bin/celery/worker/run.sh b/bin/celery/worker/run.sh new file mode 100644 index 00000000000..df2bd3fcbb0 --- /dev/null +++ b/bin/celery/worker/run.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -euo pipefail + +celery -A taskapp worker -l INFO diff --git a/docker-compose.yml b/docker-compose.yml index 240cfcce3a1..1e1e5624e84 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -93,6 +93,28 @@ services: reservations: memory: 128M + worker: &worker + restart: unless-stopped + environment: + - PYTHONUNBUFFERED=1 + - PYTHONPATH=/code/app + env_file: + - app/app/.env + build: + context: . + dockerfile: Dockerfile + image: gitcoinco/web:latest + volumes: + - .:/code + depends_on: + - db + - redis + command: ["bash","./bin/celery/worker/run.sh"] + + scheduler: + <<: *worker + command: ["bash","./bin/celery/scheduler/run.sh"] + volumes: pgdata: ipfsdata: diff --git a/requirements/base.txt b/requirements/base.txt index ba83890180d..5cd5c4f88d0 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -2,7 +2,9 @@ bs4 ccxt cryptocompare==0.6 cryptography==2.3 +celery==4.2.1 django==2.2.3 +django-celery-beat==1.1.1 django-cors-headers==2.4.0 django-filter==2.0.0 django-ratelimit==1.1.0 From c96ca9c893c2c8a1556d8f1dd2b832df68795ced Mon Sep 17 00:00:00 2001 From: Andrew Redden Date: Tue, 19 Nov 2019 21:08:12 -0400 Subject: [PATCH 2/3] added general exception handling to the bounty_email delay call logging an error of the caught exception - removed print statement --- app/marketing/mails.py | 7 ++++++- app/taskapp/celery.py | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/marketing/mails.py b/app/marketing/mails.py index 9a60accb672..073ed7b5a35 100644 --- a/app/marketing/mails.py +++ b/app/marketing/mails.py @@ -663,7 +663,12 @@ def no_applicant_reminder(to_email, bounty): def share_bounty(emails, msg, profile, invite_url=None, kudos_invite=False): from dashboard.tasks import bounty_emails - bounty_emails.delay(emails, msg, profile.handle, invite_url, kudos_invite) + # attempt to delay bounty_emails task to a worker + # long on failure to queue + try: + bounty_emails.delay(emails, msg, profile.handle, invite_url, kudos_invite) + except Exception as e: + logger.error(str(e)) def new_reserved_issue(from_email, user, bounty): diff --git a/app/taskapp/celery.py b/app/taskapp/celery.py index f0ddd3ecaed..a488c128586 100644 --- a/app/taskapp/celery.py +++ b/app/taskapp/celery.py @@ -20,8 +20,6 @@ def on_celery_setup_logging(**kwargs): def ready(self): # Using a string here means the worker will not have to - # pickle the object when using Windows. app.config_from_object('django.conf:settings', namespace='CELERY') installed_apps = [app_config.name for app_config in apps.get_app_configs()] - print(installed_apps) app.autodiscover_tasks(lambda: installed_apps, force=True) From 4426cf3d4588e053599c588f3488234a6e88734a Mon Sep 17 00:00:00 2001 From: octavioamu Date: Wed, 20 Nov 2019 11:07:34 -0300 Subject: [PATCH 3/3] rollback secrets.js --- app/assets/v2/js/lib/secrets.min.js | 244 +--------------------------- 1 file changed, 1 insertion(+), 243 deletions(-) diff --git a/app/assets/v2/js/lib/secrets.min.js b/app/assets/v2/js/lib/secrets.min.js index 75a5c5003d3..8177ff0c1fd 100644 --- a/app/assets/v2/js/lib/secrets.min.js +++ b/app/assets/v2/js/lib/secrets.min.js @@ -2,246 +2,4 @@ // @preserve author Alexander Stetsyuk // @preserve author Glenn Rempe // @license MIT -!function (a, b) { - "use strict"; - "function" == typeof define && define.amd ? define([], function () { - return a.secrets = b() - }) : "object" == typeof exports ? module.exports = b(require("crypto")) : a.secrets = b(a.crypto) -}(this, function (a) { - "use strict"; - - function b() { - p = { - bits: 8, - radix: 16, - minBits: 3, - maxBits: 20, - bytesPerChar: 2, - maxBytesPerChar: 6, - primitivePolynomials: [null, null, 1, 3, 3, 5, 3, 3, 29, 17, 9, 5, 83, 27, 43, 3, 45, 9, 39, 39, 9, 5, 3, 33, 27, 9, 71, 39, 9, 5, 83] - }, q = {}, r = new Array(1024).join("0"), s = !0, t = 10, u = ["nodeCryptoRandomBytes", "browserCryptoGetRandomValues", "browserSJCLRandom", "testRandom"] - } - - function c() { - return q && q.rng && "function" == typeof q.rng ? !0 : !1 - } - - function d(a, b) { - var c; - if (0 === b || 1 === b) return a; - if (b && b > 1024) throw new Error("Padding must be multiples of no larger than 1024 bits."); - return b = b || q.bits, a && (c = a.length % b), c ? (r + a).slice(-(b - c + a.length)) : a - } - - function e(a) { - var b, c, e = ""; - for (c = a.length - 1; c >= 0; c--) { - if (b = parseInt(a[c], 16), isNaN(b)) throw new Error("Invalid hex character."); - e = d(b.toString(2), 4) + e - } - return e - } - - function f(a) { - var b, c, e = ""; - for (a = d(a, 4), c = a.length; c >= 4; c -= 4) { - if (b = parseInt(a.slice(c - 4, c), 2), isNaN(b)) throw new Error("Invalid binary character."); - e = b.toString(16) + e - } - return e - } - - function g() { - return !a || "object" != typeof a || "function" != typeof a.getRandomValues && "object" != typeof a.getRandomValues || "function" != typeof Uint32Array && "object" != typeof Uint32Array ? !1 : !0 - } - - function h() { - return "object" == typeof a && "function" == typeof a.randomBytes ? !0 : !1 - } - - function i() { - return "object" == typeof sjcl && "object" == typeof sjcl.random ? !0 : !1 - } - - function j(b) { - function c(a, b, c, e) { - var f, g, h = 0, i = ""; - for (b && (f = b.length - 1); f > h || i.length < a;) g = Math.abs(parseInt(b[h], c)), i += d(g.toString(2), e), h++; - return i = i.substr(-a), (i.match(/0/g) || []).length === i.length ? null : i - } - - function e(b) { - var d, e, f, g, h = null; - for (f = 16, g = 4, e = Math.ceil(b / 8); null === h;) d = a.randomBytes(e), h = c(b, d.toString("hex"), f, g); - return h - } - - function f(b) { - var d, e, f, g = null; - for (e = 10, f = 32, d = Math.ceil(b / 32); null === g;) g = c(b, a.getRandomValues(new Uint32Array(d)), e, f); - return g - } - - function j(a) { - var b, d, e, f = null; - if (d = 10, e = 32, b = Math.ceil(a / 32), !sjcl.random.isReady(t)) throw new Error("SJCL isn't finished seeding the RNG yet."); - return f = c(a, sjcl.random.randomWords(b, t), d, e) - } - - function k(a) { - var b, d, e, f, g, h = null; - f = 10, g = 32, d = Math.ceil(a / 32), e = 123456789, b = new Uint32Array(d); - for (var i = 0; i < b.length; i++) b[i] = e; - for (; null === h;) h = c(a, b, f, g); - return h - } - - return b && "testRandom" === b ? (q.typeCSPRNG = b, k) : b && "nodeCryptoRandomBytes" === b ? (q.typeCSPRNG = b, e) : b && "browserCryptoGetRandomValues" === b ? (q.typeCSPRNG = b, f) : b && "browserSJCLRandom" === b ? (s = !1, q.typeCSPRNG = b, j) : h() ? (q.typeCSPRNG = "nodeCryptoRandomBytes", e) : g() ? (q.typeCSPRNG = "browserCryptoGetRandomValues", f) : i() ? (s = !1, q.typeCSPRNG = "browserSJCLRandom", j) : void 0 - } - - function k(a, b) { - var c, e = []; - for (b && (a = d(a, b)), c = a.length; c > q.bits; c -= q.bits) e.push(parseInt(a.slice(c - q.bits, c), 2)); - return e.push(parseInt(a.slice(0, c), 2)), e - } - - function l(a, b) { - var c, d = q.logs[a], e = 0; - for (c = b.length - 1; c >= 0; c--) e = 0 !== e ? q.exps[(d + q.logs[e]) % q.maxShares] ^ b[c] : b[c]; - return e - } - - function m(a, b, c) { - var d, e, f, g, h = 0; - for (f = 0, d = b.length; d > f; f++) if (c[f]) { - for (e = q.logs[c[f]], g = 0; d > g; g++) if (f !== g) { - if (a === b[g]) { - e = -1; - break - } - e = (e + q.logs[a ^ b[g]] - q.logs[b[f] ^ b[g]] + q.maxShares) % q.maxShares - } - h = -1 === e ? h : h ^ q.exps[e] - } - return h - } - - function n(a, b, c) { - var d, e, f = [], g = [a]; - for (d = 1; c > d; d++) g[d] = parseInt(q.rng(q.bits), 2); - for (d = 1, e = b + 1; e > d; d++) f[d - 1] = {x: d, y: l(d, g)}; - return f - } - - function o(a, b, c) { - var e, f, g, h, i; - if (b = parseInt(b, q.radix), a = parseInt(a, 10) || q.bits, e = a.toString(36).toUpperCase(), g = Math.pow(2, a) - 1, h = g.toString(q.radix).length, f = d(b.toString(q.radix), h), "number" != typeof b || b % 1 !== 0 || 1 > b || b > g) throw new Error("Share id must be an integer between 1 and " + g + ", inclusive."); - return i = e + f + c - } - - var p, q, r, s, t, u, v = { - init: function (a, d) { - var e, f, h = [], j = [], k = 1; - if (b(), a && ("number" != typeof a || a % 1 !== 0 || a < p.minBits || a > p.maxBits)) throw new Error("Number of bits must be an integer between " + p.minBits + " and " + p.maxBits + ", inclusive."); - if (d && -1 === u.indexOf(d)) throw new Error("Invalid RNG type argument : '" + d + "'"); - for (q.radix = p.radix, q.bits = a || p.bits, q.size = Math.pow(2, q.bits), q.maxShares = q.size - 1, e = p.primitivePolynomials[q.bits], f = 0; f < q.size; f++) j[f] = k, h[k] = f, k <<= 1, k >= q.size && (k ^= e, k &= q.maxShares); - if (q.logs = h, q.exps = j, d && this.setRNG(d), c() || this.setRNG(), i() && "browserSJCLRandom" === q.typeCSPRNG && (sjcl.random = new sjcl.prng(t), g() && sjcl.random.startCollectors(), this.seedRNG()), !(c() && q.bits && q.size && q.maxShares && q.logs && q.exps && q.logs.length === q.size && q.exps.length === q.size)) throw new Error("Initialization failed.") - }, - seedRNG: function (b, c, d) { - var e, f; - c = parseInt(c, 10), d = d || "seedRNG", i() && g() && (e = new Uint32Array(256), f = a.getRandomValues(e), sjcl.random.addEntropy(f, 2048, "cryptoGetRandomValues")), i() && h() && a.randomBytes(256, function (a, b) { - if (a) throw a; - sjcl.random.addEntropy(b.toString("hex"), 2048, "cryptoRandomBytes") - }), i() && b && c && d && "browserSJCLRandom" === q.typeCSPRNG && sjcl.random.addEntropy(b, c, d) - }, - combine: function (a, b) { - var c, g, h, i, j, l, n, o = "", p = [], r = []; - for (b = b || 0, c = 0, h = a.length; h > c; c++) { - if (l = this.extractShareComponents(a[c]), void 0 === j) j = l.bits; else if (l.bits !== j) throw new Error("Mismatched shares: Different bit settings."); - if (q.bits !== j && this.init(j), -1 === p.indexOf(l.id)) for (p.push(l.id), n = k(e(l.data)), g = 0, i = n.length; i > g; g++) r[g] = r[g] || [], r[g][p.length - 1] = n[g] - } - for (c = 0, h = r.length; h > c; c++) o = d(m(b, p, r[c]).toString(2)) + o; - return f(b >= 1 ? o : o.slice(o.indexOf("1") + 1)) - }, - getConfig: function () { - var a = {}; - return a.radix = q.radix, a.bits = q.bits, a.maxShares = q.maxShares, a.hasCSPRNG = c(), a.typeCSPRNG = q.typeCSPRNG, a - }, - extractShareComponents: function (a) { - var b, c, d, e, f, g, h = {}; - if (b = parseInt(a.substr(0, 1), 36), b && ("number" != typeof b || b % 1 !== 0 || b < p.minBits || b > p.maxBits)) throw new Error("Invalid share : Number of bits must be an integer between " + p.minBits + " and " + p.maxBits + ", inclusive."); - if (e = Math.pow(2, b) - 1, d = (Math.pow(2, b) - 1).toString(q.radix).length, f = "^([a-kA-K3-9]{1})([a-fA-F0-9]{" + d + "})([a-fA-F0-9]+)$", g = new RegExp(f).exec(a), g && (c = parseInt(g[2], q.radix)), "number" != typeof c || c % 1 !== 0 || 1 > c || c > e) throw new Error("Invalid share : Share id must be an integer between 1 and " + q.maxShares + ", inclusive."); - if (g && g[3]) return h.bits = b, h.id = c, h.data = g[3], h; - throw new Error("The share data provided is invalid : " + a) - }, - setRNG: function (a) { - var b = "Random number generator is invalid ", - c = " Supply an CSPRNG of the form function(bits){} that returns a string containing 'bits' number of random 1's and 0's."; - if (a && "string" == typeof a && -1 === u.indexOf(a)) throw new Error("Invalid RNG type argument : '" + a + "'"); - if (a || (a = j()), a && "string" == typeof a && (a = j(a)), s) { - if (a && "function" != typeof a) throw new Error(b + "(Not a function)." + c); - if (a && "string" != typeof a(q.bits)) throw new Error(b + "(Output is not a string)." + c); - if (a && !parseInt(a(q.bits), 2)) throw new Error(b + "(Binary string output not parseable to an Integer)." + c); - if (a && a(q.bits).length > q.bits) throw new Error(b + "(Output length is greater than config.bits)." + c); - if (a && a(q.bits).length < q.bits) throw new Error(b + "(Output length is less than config.bits)." + c) - } - return q.rng = a, !0 - }, - str2hex: function (a, b) { - var c, e, f, g, h, i, j = ""; - if ("string" != typeof a) throw new Error("Input must be a character string."); - if (b || (b = p.bytesPerChar), "number" != typeof b || 1 > b || b > p.maxBytesPerChar || b % 1 !== 0) throw new Error("Bytes per character must be an integer between 1 and " + p.maxBytesPerChar + ", inclusive."); - for (c = 2 * b, e = Math.pow(16, c) - 1, h = 0, i = a.length; i > h; h++) { - if (g = a[h].charCodeAt(), isNaN(g)) throw new Error("Invalid character: " + a[h]); - if (g > e) throw f = Math.ceil(Math.log(g + 1) / Math.log(256)), new Error("Invalid character code (" + g + "). Maximum allowable is 256^bytes-1 (" + e + "). To convert this character, use at least " + f + " bytes."); - j = d(g.toString(16), c) + j - } - return j - }, - hex2str: function (a, b) { - var c, e, f, g = ""; - if ("string" != typeof a) throw new Error("Input must be a hexadecimal string."); - if (b = b || p.bytesPerChar, "number" != typeof b || b % 1 !== 0 || 1 > b || b > p.maxBytesPerChar) throw new Error("Bytes per character must be an integer between 1 and " + p.maxBytesPerChar + ", inclusive."); - for (c = 2 * b, a = d(a, c), e = 0, f = a.length; f > e; e += c) g = String.fromCharCode(parseInt(a.slice(e, e + c), 16)) + g; - return g - }, - random: function (a) { - if ("number" != typeof a || a % 1 !== 0 || 2 > a || a > 65536) throw new Error("Number of bits must be an Integer between 1 and 65536."); - if ("browserSJCLRandom" === q.typeCSPRNG && sjcl.random.isReady(t) < 1) throw new Error("SJCL isn't finished seeding the RNG yet. Needs new entropy added or more mouse movement."); - return f(q.rng(a)) - }, - share: function (a, b, c, g) { - var h, i, j, l, m, p = new Array(b), r = new Array(b); - if (g = g || 128, "string" != typeof a) throw new Error("Secret must be a string."); - if ("number" != typeof b || b % 1 !== 0 || 2 > b) throw new Error("Number of shares must be an integer between 2 and 2^bits-1 (" + q.maxShares + "), inclusive."); - if (b > q.maxShares) throw h = Math.ceil(Math.log(b + 1) / Math.LN2), new Error("Number of shares must be an integer between 2 and 2^bits-1 (" + q.maxShares + "), inclusive. To create " + b + " shares, use at least " + h + " bits."); - if ("number" != typeof c || c % 1 !== 0 || 2 > c) throw new Error("Threshold number of shares must be an integer between 2 and 2^bits-1 (" + q.maxShares + "), inclusive."); - if (c > q.maxShares) throw h = Math.ceil(Math.log(c + 1) / Math.LN2), new Error("Threshold number of shares must be an integer between 2 and 2^bits-1 (" + q.maxShares + "), inclusive. To use a threshold of " + c + ", use at least " + h + " bits."); - if (c > b) throw new Error("Threshold number of shares was " + c + " but must be less than or equal to the " + b + " shares specified as the total to generate."); - if ("number" != typeof g || g % 1 !== 0 || 0 > g || g > 1024) throw new Error("Zero-pad length must be an integer between 0 and 1024 inclusive."); - for (a = "1" + e(a), a = k(a, g), j = 0, m = a.length; m > j; j++) for (i = n(a[j], b, c), l = 0; b > l; l++) p[l] = p[l] || i[l].x.toString(q.radix), r[l] = d(i[l].y.toString(2)) + (r[l] || ""); - for (j = 0; b > j; j++) p[j] = o(q.bits, p[j], f(r[j])); - return p - }, - newShare: function (a, b) { - var c; - if (a && "string" == typeof a && (a = parseInt(a, q.radix)), a && b && b[0]) return c = this.extractShareComponents(b[0]), o(c.bits, a, this.combine(b, a)); - throw new Error("Invalid 'id' or 'shares' Array argument to newShare().") - }, - _reset: b, - _padLeft: d, - _hex2bin: e, - _bin2hex: f, - _hasCryptoGetRandomValues: g, - _hasCryptoRandomBytes: h, - _hasSJCL: i, - _getRNG: j, - _isSetRNG: c, - _splitNumStringToIntArray: k, - _horner: l, - _lagrange: m, - _getShares: n, - _constructPublicShareString: o - }; - return v.init(), v -}); +!function(a,b){"use strict";"function"==typeof define&&define.amd?define([],function(){return a.secrets=b()}):"object"==typeof exports?module.exports=b(require("crypto")):a.secrets=b(a.crypto)}(this,function(a){"use strict";function b(){p={bits:8,radix:16,minBits:3,maxBits:20,bytesPerChar:2,maxBytesPerChar:6,primitivePolynomials:[null,null,1,3,3,5,3,3,29,17,9,5,83,27,43,3,45,9,39,39,9,5,3,33,27,9,71,39,9,5,83]},q={},r=new Array(1024).join("0"),s=!0,t=10,u=["nodeCryptoRandomBytes","browserCryptoGetRandomValues","browserSJCLRandom","testRandom"]}function c(){return q&&q.rng&&"function"==typeof q.rng?!0:!1}function d(a,b){var c;if(0===b||1===b)return a;if(b&&b>1024)throw new Error("Padding must be multiples of no larger than 1024 bits.");return b=b||q.bits,a&&(c=a.length%b),c?(r+a).slice(-(b-c+a.length)):a}function e(a){var b,c,e="";for(c=a.length-1;c>=0;c--){if(b=parseInt(a[c],16),isNaN(b))throw new Error("Invalid hex character.");e=d(b.toString(2),4)+e}return e}function f(a){var b,c,e="";for(a=d(a,4),c=a.length;c>=4;c-=4){if(b=parseInt(a.slice(c-4,c),2),isNaN(b))throw new Error("Invalid binary character.");e=b.toString(16)+e}return e}function g(){return!a||"object"!=typeof a||"function"!=typeof a.getRandomValues&&"object"!=typeof a.getRandomValues||"function"!=typeof Uint32Array&&"object"!=typeof Uint32Array?!1:!0}function h(){return"object"==typeof a&&"function"==typeof a.randomBytes?!0:!1}function i(){return"object"==typeof sjcl&&"object"==typeof sjcl.random?!0:!1}function j(b){function c(a,b,c,e){var f,g,h=0,i="";for(b&&(f=b.length-1);f>h||i.lengthq.bits;c-=q.bits)e.push(parseInt(a.slice(c-q.bits,c),2));return e.push(parseInt(a.slice(0,c),2)),e}function l(a,b){var c,d=q.logs[a],e=0;for(c=b.length-1;c>=0;c--)e=0!==e?q.exps[(d+q.logs[e])%q.maxShares]^b[c]:b[c];return e}function m(a,b,c){var d,e,f,g,h=0;for(f=0,d=b.length;d>f;f++)if(c[f]){for(e=q.logs[c[f]],g=0;d>g;g++)if(f!==g){if(a===b[g]){e=-1;break}e=(e+q.logs[a^b[g]]-q.logs[b[f]^b[g]]+q.maxShares)%q.maxShares}h=-1===e?h:h^q.exps[e]}return h}function n(a,b,c){var d,e,f=[],g=[a];for(d=1;c>d;d++)g[d]=parseInt(q.rng(q.bits),2);for(d=1,e=b+1;e>d;d++)f[d-1]={x:d,y:l(d,g)};return f}function o(a,b,c){var e,f,g,h,i;if(b=parseInt(b,q.radix),a=parseInt(a,10)||q.bits,e=a.toString(36).toUpperCase(),g=Math.pow(2,a)-1,h=g.toString(q.radix).length,f=d(b.toString(q.radix),h),"number"!=typeof b||b%1!==0||1>b||b>g)throw new Error("Share id must be an integer between 1 and "+g+", inclusive.");return i=e+f+c}var p,q,r,s,t,u,v={init:function(a,d){var e,f,h=[],j=[],k=1;if(b(),a&&("number"!=typeof a||a%1!==0||ap.maxBits))throw new Error("Number of bits must be an integer between "+p.minBits+" and "+p.maxBits+", inclusive.");if(d&&-1===u.indexOf(d))throw new Error("Invalid RNG type argument : '"+d+"'");for(q.radix=p.radix,q.bits=a||p.bits,q.size=Math.pow(2,q.bits),q.maxShares=q.size-1,e=p.primitivePolynomials[q.bits],f=0;f=q.size&&(k^=e,k&=q.maxShares);if(q.logs=h,q.exps=j,d&&this.setRNG(d),c()||this.setRNG(),i()&&"browserSJCLRandom"===q.typeCSPRNG&&(sjcl.random=new sjcl.prng(t),g()&&sjcl.random.startCollectors(),this.seedRNG()),!(c()&&q.bits&&q.size&&q.maxShares&&q.logs&&q.exps&&q.logs.length===q.size&&q.exps.length===q.size))throw new Error("Initialization failed.")},seedRNG:function(b,c,d){var e,f;c=parseInt(c,10),d=d||"seedRNG",i()&&g()&&(e=new Uint32Array(256),f=a.getRandomValues(e),sjcl.random.addEntropy(f,2048,"cryptoGetRandomValues")),i()&&h()&&a.randomBytes(256,function(a,b){if(a)throw a;sjcl.random.addEntropy(b.toString("hex"),2048,"cryptoRandomBytes")}),i()&&b&&c&&d&&"browserSJCLRandom"===q.typeCSPRNG&&sjcl.random.addEntropy(b,c,d)},combine:function(a,b){var c,g,h,i,j,l,n,o="",p=[],r=[];for(b=b||0,c=0,h=a.length;h>c;c++){if(l=this.extractShareComponents(a[c]),void 0===j)j=l.bits;else if(l.bits!==j)throw new Error("Mismatched shares: Different bit settings.");if(q.bits!==j&&this.init(j),-1===p.indexOf(l.id))for(p.push(l.id),n=k(e(l.data)),g=0,i=n.length;i>g;g++)r[g]=r[g]||[],r[g][p.length-1]=n[g]}for(c=0,h=r.length;h>c;c++)o=d(m(b,p,r[c]).toString(2))+o;return f(b>=1?o:o.slice(o.indexOf("1")+1))},getConfig:function(){var a={};return a.radix=q.radix,a.bits=q.bits,a.maxShares=q.maxShares,a.hasCSPRNG=c(),a.typeCSPRNG=q.typeCSPRNG,a},extractShareComponents:function(a){var b,c,d,e,f,g,h={};if(b=parseInt(a.substr(0,1),36),b&&("number"!=typeof b||b%1!==0||bp.maxBits))throw new Error("Invalid share : Number of bits must be an integer between "+p.minBits+" and "+p.maxBits+", inclusive.");if(e=Math.pow(2,b)-1,d=(Math.pow(2,b)-1).toString(q.radix).length,f="^([a-kA-K3-9]{1})([a-fA-F0-9]{"+d+"})([a-fA-F0-9]+)$",g=new RegExp(f).exec(a),g&&(c=parseInt(g[2],q.radix)),"number"!=typeof c||c%1!==0||1>c||c>e)throw new Error("Invalid share : Share id must be an integer between 1 and "+q.maxShares+", inclusive.");if(g&&g[3])return h.bits=b,h.id=c,h.data=g[3],h;throw new Error("The share data provided is invalid : "+a)},setRNG:function(a){var b="Random number generator is invalid ",c=" Supply an CSPRNG of the form function(bits){} that returns a string containing 'bits' number of random 1's and 0's.";if(a&&"string"==typeof a&&-1===u.indexOf(a))throw new Error("Invalid RNG type argument : '"+a+"'");if(a||(a=j()),a&&"string"==typeof a&&(a=j(a)),s){if(a&&"function"!=typeof a)throw new Error(b+"(Not a function)."+c);if(a&&"string"!=typeof a(q.bits))throw new Error(b+"(Output is not a string)."+c);if(a&&!parseInt(a(q.bits),2))throw new Error(b+"(Binary string output not parseable to an Integer)."+c);if(a&&a(q.bits).length>q.bits)throw new Error(b+"(Output length is greater than config.bits)."+c);if(a&&a(q.bits).lengthb||b>p.maxBytesPerChar||b%1!==0)throw new Error("Bytes per character must be an integer between 1 and "+p.maxBytesPerChar+", inclusive.");for(c=2*b,e=Math.pow(16,c)-1,h=0,i=a.length;i>h;h++){if(g=a[h].charCodeAt(),isNaN(g))throw new Error("Invalid character: "+a[h]);if(g>e)throw f=Math.ceil(Math.log(g+1)/Math.log(256)),new Error("Invalid character code ("+g+"). Maximum allowable is 256^bytes-1 ("+e+"). To convert this character, use at least "+f+" bytes.");j=d(g.toString(16),c)+j}return j},hex2str:function(a,b){var c,e,f,g="";if("string"!=typeof a)throw new Error("Input must be a hexadecimal string.");if(b=b||p.bytesPerChar,"number"!=typeof b||b%1!==0||1>b||b>p.maxBytesPerChar)throw new Error("Bytes per character must be an integer between 1 and "+p.maxBytesPerChar+", inclusive.");for(c=2*b,a=d(a,c),e=0,f=a.length;f>e;e+=c)g=String.fromCharCode(parseInt(a.slice(e,e+c),16))+g;return g},random:function(a){if("number"!=typeof a||a%1!==0||2>a||a>65536)throw new Error("Number of bits must be an Integer between 1 and 65536.");if("browserSJCLRandom"===q.typeCSPRNG&&sjcl.random.isReady(t)<1)throw new Error("SJCL isn't finished seeding the RNG yet. Needs new entropy added or more mouse movement.");return f(q.rng(a))},share:function(a,b,c,g){var h,i,j,l,m,p=new Array(b),r=new Array(b);if(g=g||128,"string"!=typeof a)throw new Error("Secret must be a string.");if("number"!=typeof b||b%1!==0||2>b)throw new Error("Number of shares must be an integer between 2 and 2^bits-1 ("+q.maxShares+"), inclusive.");if(b>q.maxShares)throw h=Math.ceil(Math.log(b+1)/Math.LN2),new Error("Number of shares must be an integer between 2 and 2^bits-1 ("+q.maxShares+"), inclusive. To create "+b+" shares, use at least "+h+" bits.");if("number"!=typeof c||c%1!==0||2>c)throw new Error("Threshold number of shares must be an integer between 2 and 2^bits-1 ("+q.maxShares+"), inclusive.");if(c>q.maxShares)throw h=Math.ceil(Math.log(c+1)/Math.LN2),new Error("Threshold number of shares must be an integer between 2 and 2^bits-1 ("+q.maxShares+"), inclusive. To use a threshold of "+c+", use at least "+h+" bits.");if(c>b)throw new Error("Threshold number of shares was "+c+" but must be less than or equal to the "+b+" shares specified as the total to generate.");if("number"!=typeof g||g%1!==0||0>g||g>1024)throw new Error("Zero-pad length must be an integer between 0 and 1024 inclusive.");for(a="1"+e(a),a=k(a,g),j=0,m=a.length;m>j;j++)for(i=n(a[j],b,c),l=0;b>l;l++)p[l]=p[l]||i[l].x.toString(q.radix),r[l]=d(i[l].y.toString(2))+(r[l]||"");for(j=0;b>j;j++)p[j]=o(q.bits,p[j],f(r[j]));return p},newShare:function(a,b){var c;if(a&&"string"==typeof a&&(a=parseInt(a,q.radix)),a&&b&&b[0])return c=this.extractShareComponents(b[0]),o(c.bits,a,this.combine(b,a));throw new Error("Invalid 'id' or 'shares' Array argument to newShare().")},_reset:b,_padLeft:d,_hex2bin:e,_bin2hex:f,_hasCryptoGetRandomValues:g,_hasCryptoRandomBytes:h,_hasSJCL:i,_getRNG:j,_isSetRNG:c,_splitNumStringToIntArray:k,_horner:l,_lagrange:m,_getShares:n,_constructPublicShareString:o};return v.init(),v}); \ No newline at end of file