Confused about EncryptionPlugin's 'Encrypted Vault'

150 views
Skip to first unread message

sam paul

unread,
Nov 19, 2024, 8:05:57 AM11/19/24
to TiddlyWikiClassic
Hi Yakov.

I just tried your EncryptionPlugin. It is so nice to have an AES encryption plugin for twc. Previously I used TiddlerEncryptionPlugin ( https://web.archive.org/web/20141120144214/http://www.remotely-helpful.com/TiddlyWiki/TiddlerEncryptionPlugin.html#TiddlerEncryptionPlugin ). It uses TEA algorithm which is kind of weak.

I followed the instruction to install this plugin and set a password. Then I saved 3 times. But the wiki is not encrypted when I reloaded it. I don't see any file similar to 'Encrypted Vault' in its directory, nor inside the wiki. What does the 'Encrypted Vault' mean?

Another question. I wonder how it stores passwords. If I type the wrong password. Will it display garbled strings, or say some messages like 'wrong password'? TiddlerEncryptionPlugin won't decrypt the content in this case. I don't know how it judges the correctness of passwords.

Best.

Yakov

unread,
Nov 20, 2024, 1:49:54 AM11/20/24
to TiddlyWikiClassic
Hi Sam,

let's check some bits to understand what's going on:
  1. Did you get the "The encrypted vault has been created" message?
  2. Does your TW have the "<!--PRE-VAULTSTYLE-START-->" inside it? I mean, if you open the TW file as plain text search through it.
  3. Did you set the password after saving and reloading after that message? You wrote that you set a password first and saved 3 times, but the current installation has 3 stages:
    1. install the plugin, save, reload;
    2. save (the plugin creates the vault area), reload;
    3. set the password, save (at this point you should see message "🔒 Successfully set password"), reload (the content is expected to be encrypted at this point).
'Encrypted Vault' is not a separate file, it is the separate area inside the TW (between the "<!--PRE-VAULTSTYLE-START-->" and "!--PRE-VAULTSTYLE-END-->" markers). This is an outdated approach inherited from TiddlerEncryptionPlugin which makes it necessary to do an additional installation step (2) and I'd like to rewrite it to use just a tiddler for the vault (this would also make the plugin compatible with some advanced savers and allow core upgrading without decrypting the TW), but haven't done this yet.

As for password storage, of course the password isn't stored anywhere. You can look up the logic in the decrypt method: the sjcl["decrypt"] throws an error if the password is incorrect (I guess, some checksums don't match), the plugin catches it and acts accordingly. A deeper understanding of how sjcl works will require reading its source or maybe docs.

Best regards,
Yakov.

вторник, 19 ноября 2024 г. в 16:05:57 UTC+3, samp...@gmail.com:

sam paul

unread,
Nov 20, 2024, 9:36:27 AM11/20/24
to TiddlyWikiClassic
Hi, Yakov.

I see
<!--PRE-VAULTSTYLE-START-->


<style type="text/css">


#vaultArea { display: none; }


</style>


<!--PRE-VAULTSTYLE-END-->
in the source file after I did what you said. It is not encrypted. Also, I thought this plugin would encrypt the whole wiki by default. How to add tiddlers to the encryption list?

Best.

sam paul

unread,
Nov 22, 2024, 4:25:27 AM11/22/24
to TiddlyWikiClassic
I use the k2blog theme. Will this cause the problem? Also, I test this plugin on an empty twc. I can't decrypt.

sam paul

unread,
Nov 22, 2024, 6:12:23 AM11/22/24
to TiddlyWikiClassic
 I tried again. It works on an empty twc. But not on my wiki. I use continuous saving plugin and modifed the default page and sidebar. I don't know if this cause the problem.

Yakov

unread,
Nov 22, 2024, 7:41:15 AM11/22/24
to TiddlyWikiClassic
Hi Sam,

I don't think k2blog theme will cause any trouble (by the way, thanks for pointing, maybe I'll update it a bit in TiddlyThemes, as it's not packed as an actual single-tiddler theme yet, like many others there).

CSP shouldn't produce any problem either, in fact it's a "very native" way of saving that isn't hurt by browser limitations (if the browser supports it).

Sorry, I wasn't accurate enough about the expected result. The VAULTSTYLE-START part is expected indeed, but the most important part is <div id="vaultArea"> which is the start of the encrypted content storage. Do you have it?

> I thought this plugin would encrypt the whole wiki by default. How to add tiddlers to the encryption list?
Like the docs say (although not very consistently):
  • By default, only the system tiddlers (shadow ones and those tagged systemConfig) aren't encrypted.
  • If you create [[ListUnencrypted]] and put a filter there, the filtered tiddlers won't be encrypted, too.
  • Shadow tiddlers can be encrypted; the shadow version is used until unlocking (decrypting).
  • Tiddlers tagged with unencrypted won't be encrypted; tiddlers tagged with forceEncryption will be encrypted (even customized shadows, like MainMenu).
Basically, your content tiddlers are expected to be encrypted indeed.

Back to handling your issue. Did I get it right that for both your TW (2.10.1?) and a new TW (2.10.1) you did:
  1. installed the plugin, saved and reloaded;
  2. saved, got the message "The encrypted vault has been created", and reloaded; //note: I don't remember if reloading is really necessary at this point
    • at this point <div id="vaultArea"> is in TW text (if opened with something like notepad);
  3. set the password, saved, got the "🔒 Successfully set password" message, and reloaded;
and until that point everything went similarly for your TW and a new TW, but after reloading the new TW got encrypted while your TW opened as usual?

Best regards,
Yakov.

PS notes for myself:
  • clarify in docs:
    • what is expected to be encrypted (now spread between Description and Usage sections);
    • presumably the setup process in more detail (see above);
    • maybe clarify the behavior on incorrect password and "password storage" question; maybe learn what sjcl
  • once I have time, get rid of the vaultArea to simplify setup and core upgrading;
  • [see also note on k2blog above].
пятница, 22 ноября 2024 г. в 14:12:23 UTC+3, samp...@gmail.com:

sam paul

unread,
Nov 22, 2024, 8:20:39 PM11/22/24
to TiddlyWikiClassic
Hi, Yakov,
The <div id="vaultArea"> part is below

<!--POST-SHADOWAREA-->
<div id="vaultArea">Sjcl@{"iv":"MTqORa2Q9g6MVEddhcCqaQ==","v":1,"iter":10000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"DpZOMLd8y+4=","ct":"5aAChimaQ9HUazxA9+/ here is some really large strings containing private information">
</div>
<!--POST-VAULTAREA-->
<div id="storeArea">

It seems that after encryption, the other tiddlers are not removed. There are non system tiddlers after the above lines. After I type the correct password and click unlockvault, the wiki says 
"The following tiddler tiddler already exist in system store. Overload? 
OK: the encrypted version will replace the system store version 
Cancel: the system store version will replace the encrypted version"

Best.

Yakov

unread,
Nov 25, 2024, 9:04:03 AM11/25/24
to TiddlyWikiClassic
Right, this looks like some kind of plugin conflict.

Let's try to debug this further: pick a tiddler that you expect to be encrypted, and put this into another tiddler (substitute "the tiddler" with the chosen tiddler's title):

<<tiddler {{
const title = "the tiddler"
const tiddler = store.fetchTiddler(title)
alert("shouldEncryptTiddler: " + config.extensions.vault.shouldEncryptTiddler(tiddler) + "\n" +
"doNotSave: " + tiddler.doNotSave())
""
}}>>

Let me know the result. Normally, you should get
shouldEncryptTiddler: true
doNotSave: false
at this point. Knowing the actual result will help narrowing down the issue.

Alternatively, you can try disabling some plugins that deal with storage and saving tiddlers (and check if it fixes the encryption behavior), but for now I have no educated guess on which plugins to check. Maybe try to look for plugins that contain updateOriginal or allTiddlersAsHtml.

суббота, 23 ноября 2024 г. в 04:20:39 UTC+3, samp...@gmail.com:

sam paul

unread,
Nov 26, 2024, 10:17:00 AM11/26/24
to TiddlyWikiClassic
I get
shouldEncryptTiddler: true
doNotSave: undefined

Yakov

unread,
Dec 9, 2024, 4:27:36 AM12/9/24
to TiddlyWikiClassic
Hello Sam, sorry for a long delay,

this is quite a puzzling issue, I think the only way to debug this is to modify EncryptionPlugin itself and see some debugging messages.

If you are ok with it, let's add:
  1. in updateOriginal, after !ceVault.isLocked() || ceVault.shouldPurge on the next line:
    alert(`vaultIsUpdatable: ${vaultIsUpdatable} (locateVaultArea: ${!!ceVault.locateVaultArea(original)}, isLocked : ${!!ceVault.isLocked()}, shouldPurge: ${ceVault.shouldPurge})`)
  2. in allUnencryptedTiddlersAsHtml, before var result = this.getSaver().externalize(store) (note that there's another such line in allEncryptedTiddlersAsHtml) on the next lines:
    const title = "the tiddler" // put here the name of the tiddler that you expect to be encrypted, but which is not, again
    const tiddler = store.fetchTiddler(title)
    alert(`shouldEncryptTiddler: ${config.extensions.vault.shouldEncryptTiddler(tiddler)}, doNotSave: ${
    tiddler.doNotSave()}`)
save, reload, enter the password to unencrypt, and save (with encryption) again.

The expected messages are:
  1. vaultIsUpdatable: true (locateVaultArea: true, isLocked : false, shouldPurge: false)
  2. shouldEncryptTiddler: true, doNotSave: true
(please also re-check that the tiddler is still saved in unencrypted form, too)

If we don't find any pointers using this, I think rewriting the plugin without a custom vault area will be the last resort, but let's check this first.

Ah, also you can search your TW and list here plugins that contain updateOriginal (ideally, with their Source links, so that I can look them up quickly).

Best regards,
Yakov.

PS I've packed k2blog into an actual theme, so you may want to try it (don't forget to set txtTheme option to K2BlogTheme, like using SystemSettings in the demo TW). I'll probably add some compatibility improvements there later, so if you're using EEP, you'll get notified once they are available.

вторник, 26 ноября 2024 г. в 18:17:00 UTC+3, samp...@gmail.com:

sam paul

unread,
Dec 10, 2024, 4:30:27 AM12/10/24
to TiddlyWikiClassic
Hi Yakov.

I tried to debug in this way. But there are too many tiddlers and so much alert asking for whether to replace the tiddler of the same name with the encrypted content. I guess this debugging alert is after these hundreds of alerts. Is there another method?

Best.

Yakov

unread,
Dec 11, 2024, 10:44:28 AM12/11/24
to TiddlyWikiClassic
Right, we can work around this by temporarily suppressing those dialogs:

take the line if(store.getTiddler(title) && !confirm(config.messages.confirmOverload +"\n\n"+ title))
and comment out the confirmation: if(store.getTiddler(title))// && !confirm(config.messages.confirmOverload +"\n\n"+ title))
later, you can remove the added characters, and the dialog will be resumed.

Since the unencrypted tiddlers presumably have the same text as the encrypted ones, this dialog makes no sense for now and shouldn't affect the debugging process. However, please check once that they do have the same content as before the encryption.

вторник, 10 декабря 2024 г. в 12:30:27 UTC+3, samp...@gmail.com:

sam paul

unread,
Dec 11, 2024, 7:32:45 PM12/11/24
to TiddlyWikiClassic
The page says
vaultIsUpdatable: false (locateVaultArea: true, isLocked : true, shouldPurge: false)

Yakov

unread,
Dec 13, 2024, 9:26:08 AM12/13/24
to TiddlyWikiClassic
Right, this definitely sheds some light on the case! The catch is, isLocked gives true while is expected to give false.

Now let's remove these bits:
  1. in updateOriginal, after !ceVault.isLocked() || ceVault.shouldPurge on the next line:
    alert(`vaultIsUpdatable: ${vaultIsUpdatable} (locateVaultArea: ${!!ceVault.locateVaultArea(original)}, isLocked : ${!!ceVault.isLocked()}, shouldPurge: ${ceVault.shouldPurge})`)
  2. in allUnencryptedTiddlersAsHtml, before var result = this.getSaver().externalize(store) (note that there's another such line in allEncryptedTiddlersAsHtml) on the next lines:
    const title = "the tiddler" // put here the name of the tiddler that you expect to be encrypted, but which is not, again
    const tiddler = store.fetchTiddler(title)
    alert(`shouldEncryptTiddler: ${config.extensions.vault.shouldEncryptTiddler(tiddler)}, doNotSave: ${
    tiddler.doNotSave()}`)
and instead:
  1. add bold text to isLocked: function() { ... }:
    isLocked: function(doDebug) {
      if(
    doDebug) alert(`this.loaded: ${this.loaded}`)
      if(
    this.loaded) return false
      var vaultContent = this.getVaultContent()

      if(doDebug) alert(vaultContent ? `vaultContent starts with ${vaultContent.substr(0, this.prefix.length)}` :
        `vaultContent is ${vaultContent}`)
      return
    vaultContent === null || this.isEncrypted(vaultContent)
    }
  2. in updateOriginal, substitute ceVault.isLocked() with ceVault.isLocked(true)
[and save, reload, enter the password to unencrypt, and save (with encryption) again]

Since you're saying that  <div id="vaultArea"> is there, it's most likely that you'll either get
  • one message "this.loaded: false" or
  • two messages, "this.loaded: true" and "vaultContent starts with ..."
Let's check this, I think we're close.

четверг, 12 декабря 2024 г. в 03:32:45 UTC+3, samp...@gmail.com:

sam paul

unread,
Dec 13, 2024, 11:47:52 AM12/13/24
to TiddlyWikiClassic
Hi Yakov. I did as you said and conment the confirmation and save again. But the plugin manager says
Error: SyntaxError: Unexpected token 'return'

Yakov

unread,
Dec 14, 2024, 5:16:41 AM12/14/24
to TiddlyWikiClassic
Hi Sam, not really sure what's wrong: I've tested the additions I've proposed and the don't produce such an error on my side.

To simplify things, I'm attaching the changed plugin that I've tested in a file, let me know if the problem stays with it:

пятница, 13 декабря 2024 г. в 19:47:52 UTC+3, samp...@gmail.com:
EncryptionPlugin-with-alert.txt

sam paul

unread,
Dec 14, 2024, 10:08:50 AM12/14/24
to TiddlyWikiClassic
HI Yakov. I also change if(store.getTiddler(title) && !confirm(config.messages.confirmOverload +"\n\n"+ title)) to if(store.getTiddler(title)// && !confirm(config.messages.confirmOverload +"\n\n"+ title)) to avoid too many windows alert. I think this comment is causing the error.

Yakov

unread,
Dec 23, 2024, 2:50:31 AM12/23/24
to TiddlyWikiClassic
Ah, yes, I can see that you've missed a closing bracket here (that's why I've marked all 3 added symbols with bold): my suggestions was


if(store.getTiddler(title))// && !confirm(config.messages.confirmOverload +"\n\n"+ title))
but you added only the comment part:
if(store.getTiddler(title)// && !confirm(config.messages.confirmOverload +"\n\n"+ title))

Add the missing bracket to fix this and let's see what we get in the debugging messages.

суббота, 14 декабря 2024 г. в 18:08:50 UTC+3, samp...@gmail.com:

sam paul

unread,
Dec 25, 2024, 10:47:14 AM12/25/24
to TiddlyWikiClassic
Thanks.
I test the plugin with alert provided in the attached text file.
It says:
this.loaded: undefined
vaultContent is
Failed to save main TiddlyWiki file. Your changes have not been saved
TypeError: Cannot read properties of undefined (reading 'length')

Yakov

unread,
Jan 5, 2025, 6:28:02 AMJan 5
to TiddlyWikiClassic
Hello Sam,

I've decided to create a new version of EncryptionPlugin (the one that uses a tiddler as the vault) and while working on it, I've come up with a change that makes the plugin more robust and may fix your issue as well. Could you test this one?

See it below (the changes can be found by searching by "doNotSaveMode"); this one doesn't include the switch to the tiddler store, which will be released later.

/***
|Name        |EncryptionPlugin|
|Description |Adds AES encryption and password protection and several macros to work with them|
|Version     |1.8.0|
|Source      |https://github.com/YakovL/TiddlyWiki_EncryptionPlugin/blob/master/EncryptionPlugin.js|
|Author      |Yakov Litvin|
|Forked from |[[EncryptedVaultPlugin|http://visualtw.ouvaton.org/VisualTW.html#EncryptedVaultPlugin]] by Pascal Collin (now available at [[GitHub|https://yakovl.github.io/VisualTW2/VisualTW2.html#EncryptedVaultPlugin]] or [[in web archive|https://web.archive.org/web/20160130130224/http://visualtw.ouvaton.org/VisualTW.html#EncryptedVaultPlugin]]); versions up to 1.7.1 were pre-released as ~NewEncryptedVaultPlugin|
|Browser     |The plugin is supposed to work in any modern browser, but it is recommended to create a backup of your TW before trying it with your setup|
|''License:''|[[BSD open source license|License]]|
!Description
* Create an ''encrypted vault'' where all tiddlers are ''password protected''.
* By default, only the system tiddlers (shadow ones and those tagged {{{systemConfig}}}) aren't encrypted. Also, if you create [[ListUnencrypted]] and put a [[filter|classic.tiddlywiki.com#Filters]] there, the filtered tiddlers won't be encrypted, too.
* Even shadow tiddlers (MainMenu, SiteTitle, PageTemplate, StyleSheet, ...) ''can be encrypted''. The shadow version is used until unlocking.
!Demo
--Use <<unlock>> button on a protected wiki. By example: http://visualtw.ouvaton.org/demo/EncryptedVaultPlugin.html--
!Installation
# Import/copy the plugin (tagged with {{{systemConfig}}}), save and reload (as usual)
# Save one more time (to create the encrypted vault) and reload
# Set a password
!Usage
* TODO: retest and describe backward compatibility/migration from ~EncryptedVaultPlugin
* Use <<unlock>><<setPassword>> button (available by default in SideBarOptions)
* Use a blank password to save unencrypted (disable vault usage)
* Use {{{unencrypted}}} tag to avoid encryption for some tiddler
* Use {{{forceEncryption}}} tag to force some shadow tiddler to be encrypted
* TODO: describe other macros
!Configuration
The following macros are available:
* {{{<<unlock ButtonTitle ButtonTooltip OpenTiddlersWhenUnlock CloseTiddlersWhenUnlock>>}}} creates a button to unlock the encrypted vault (all parameters are optional)
* {{{<<setPassword ButtonTitle ButtonTooltip>>}}} if unlocked, creates a button to set the current password (all parameters are optional)
* {{{<<purge ButtonTitle ButtonTooltip>>}}} if locked, creates a button to purge a locked vault, useful for lost password (encrypted content is the deleted)
* {{{<<ifLocked tiddlyText>>}}} displays tiddlyText (wikified) if the vault is locked
* {{{<<ifUnlocked tiddlyText>>}}} displays tiddlyText (wikified) if the vault is unlocked
<<ifLocked "!!!!Lost password ?">><<ifLocked "Click on">> <<purge>><<ifLocked "to delete any content locked in the encrypted vault.">>
!Changes since ~EncryptedVaultPlugin
Compared to ~EncryptedVaultPlugin, this plugin:
* Fixes various major issues, uses decorators instead of overridings (of _);
* Uses a customizable StyleSheetVault shadow for styles;
* Improves the UI for password (doesn't show the typed value, adds autofocus, supports pressing enter to apply).
***/
/***
Stanford Javascript Crypto Library {{DDnc{v.1.0.6 ([[63eed5|https://github.com/bitwiseshiftleft/sjcl/commit/63eed58b9dc395afb3c03df8d70d7e7bf4c88b1b]]), update!}}}, source: https://github.com/bitwiseshiftleft/sjcl
+ one line to make `sjcl` globally accessible
***/
//{{{
"use strict";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(a){this.toString=function(){return"CORRUPT: "+this.message};this.message=a},invalid:function(a){this.toString=function(){return"INVALID: "+this.message};this.message=a},bug:function(a){this.toString=function(){return"BUG: "+this.message};this.message=a},notReady:function(a){this.toString=function(){return"NOT READY: "+this.message};this.message=a}}};
sjcl.cipher.aes=function(a){this.s[0][0][0]||this.O();var b,c,d,e,f=this.s[0][4],g=this.s[1];b=a.length;var h=1;if(4!==b&&6!==b&&8!==b)throw new sjcl.exception.invalid("invalid aes key size");this.b=[d=a.slice(0),e=[]];for(a=b;a<4*b+28;a++){c=d[a-1];if(0===a%b||8===b&&4===a%b)c=f[c>>>24]<<24^f[c>>16&255]<<16^f[c>>8&255]<<8^f[c&255],0===a%b&&(c=c<<8^c>>>24^h<<24,h=h<<1^283*(h>>7));d[a]=d[a-b]^c}for(b=0;a;b++,a--)c=d[b&3?a:a-4],e[b]=4>=a||4>b?c:g[0][f[c>>>24]]^g[1][f[c>>16&255]]^g[2][f[c>>8&255]]^g[3][f[c&
255]]};
sjcl.cipher.aes.prototype={encrypt:function(a){return t(this,a,0)},decrypt:function(a){return t(this,a,1)},s:[[[],[],[],[],[]],[[],[],[],[],[]]],O:function(){var a=this.s[0],b=this.s[1],c=a[4],d=b[4],e,f,g,h=[],k=[],l,n,m,p;for(e=0;0x100>e;e++)k[(h[e]=e<<1^283*(e>>7))^e]=e;for(f=g=0;!c[f];f^=l||1,g=k[g]||1)for(m=g^g<<1^g<<2^g<<3^g<<4,m=m>>8^m&255^99,c[f]=m,d[m]=f,n=h[e=h[l=h[f]]],p=0x1010101*n^0x10001*e^0x101*l^0x1010100*f,n=0x101*h[m]^0x1010100*m,e=0;4>e;e++)a[e][f]=n=n<<24^n>>>8,b[e][m]=p=p<<24^p>>>8;for(e=
0;5>e;e++)a[e]=a[e].slice(0),b[e]=b[e].slice(0)}};
function t(a,b,c){if(4!==b.length)throw new sjcl.exception.invalid("invalid aes block size");var d=a.b[c],e=b[0]^d[0],f=b[c?3:1]^d[1],g=b[2]^d[2];b=b[c?1:3]^d[3];var h,k,l,n=d.length/4-2,m,p=4,r=[0,0,0,0];h=a.s[c];a=h[0];var q=h[1],v=h[2],w=h[3],x=h[4];for(m=0;m<n;m++)h=a[e>>>24]^q[f>>16&255]^v[g>>8&255]^w[b&255]^d[p],k=a[f>>>24]^q[g>>16&255]^v[b>>8&255]^w[e&255]^d[p+1],l=a[g>>>24]^q[b>>16&255]^v[e>>8&255]^w[f&255]^d[p+2],b=a[b>>>24]^q[e>>16&255]^v[f>>8&255]^w[g&255]^d[p+3],p+=4,e=h,f=k,g=l;for(m=
0;4>m;m++)r[c?3&-m:m]=x[e>>>24]<<24^x[f>>16&255]<<16^x[g>>8&255]<<8^x[b&255]^d[p++],h=e,e=f,f=g,g=b,b=h;return r}
sjcl.bitArray={bitSlice:function(a,b,c){a=sjcl.bitArray.$(a.slice(b/32),32-(b&31)).slice(1);return void 0===c?a:sjcl.bitArray.clamp(a,c-b)},extract:function(a,b,c){var d=Math.floor(-b-c&31);return((b+c-1^b)&-32?a[b/32|0]<<32-d^a[b/32+1|0]>>>d:a[b/32|0]>>>d)&(1<<c)-1},concat:function(a,b){if(0===a.length||0===b.length)return a.concat(b);var c=a[a.length-1],d=sjcl.bitArray.getPartial(c);return 32===d?a.concat(b):sjcl.bitArray.$(b,d,c|0,a.slice(0,a.length-1))},bitLength:function(a){var b=a.length;return 0===
b?0:32*(b-1)+sjcl.bitArray.getPartial(a[b-1])},clamp:function(a,b){if(32*a.length<b)return a;a=a.slice(0,Math.ceil(b/32));var c=a.length;b=b&31;0<c&&b&&(a[c-1]=sjcl.bitArray.partial(b,a[c-1]&2147483648>>b-1,1));return a},partial:function(a,b,c){return 32===a?b:(c?b|0:b<<32-a)+0x10000000000*a},getPartial:function(a){return Math.round(a/0x10000000000)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return!1;var c=0,d;for(d=0;d<a.length;d++)c|=a[d]^b[d];return 0===
c},$:function(a,b,c,d){var e;e=0;for(void 0===d&&(d=[]);32<=b;b-=32)d.push(c),c=0;if(0===b)return d.concat(a);for(e=0;e<a.length;e++)d.push(c|a[e]>>>b),c=a[e]<<32-b;e=a.length?a[a.length-1]:0;a=sjcl.bitArray.getPartial(e);d.push(sjcl.bitArray.partial(b+a&31,32<b+a?c:d.pop(),1));return d},i:function(a,b){return[a[0]^b[0],a[1]^b[1],a[2]^b[2],a[3]^b[3]]},byteswapM:function(a){var b,c;for(b=0;b<a.length;++b)c=a[b],a[b]=c>>>24|c>>>8&0xff00|(c&0xff00)<<8|c<<24;return a}};
sjcl.codec.utf8String={fromBits:function(a){var b="",c=sjcl.bitArray.bitLength(a),d,e;for(d=0;d<c/8;d++)0===(d&3)&&(e=a[d/4]),b+=String.fromCharCode(e>>>24),e<<=8;return decodeURIComponent(escape(b))},toBits:function(a){a=unescape(encodeURIComponent(a));var b=[],c,d=0;for(c=0;c<a.length;c++)d=d<<8|a.charCodeAt(c),3===(c&3)&&(b.push(d),d=0);c&3&&b.push(sjcl.bitArray.partial(8*(c&3),d));return b}};
sjcl.codec.hex={fromBits:function(a){var b="",c;for(c=0;c<a.length;c++)b+=((a[c]|0)+0xf00000000000).toString(16).substr(4);return b.substr(0,sjcl.bitArray.bitLength(a)/4)},toBits:function(a){var b,c=[],d;a=a.replace(/\s|0x/g,"");d=a.length;a=a+"00000000";for(b=0;b<a.length;b+=8)c.push(parseInt(a.substr(b,8),16)^0);return sjcl.bitArray.clamp(c,4*d)}};
sjcl.codec.base32={B:"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",X:"0123456789ABCDEFGHIJKLMNOPQRSTUV",BITS:32,BASE:5,REMAINING:27,fromBits:function(a,b,c){var d=sjcl.codec.base32.BASE,e=sjcl.codec.base32.REMAINING,f="",g=0,h=sjcl.codec.base32.B,k=0,l=sjcl.bitArray.bitLength(a);c&&(h=sjcl.codec.base32.X);for(c=0;f.length*d<l;)f+=h.charAt((k^a[c]>>>g)>>>e),g<d?(k=a[c]<<d-g,g+=e,c++):(k<<=d,g-=d);for(;f.length&7&&!b;)f+="=";return f},toBits:function(a,b){a=a.replace(/\s|=/g,"").toUpperCase();var c=sjcl.codec.base32.BITS,
d=sjcl.codec.base32.BASE,e=sjcl.codec.base32.REMAINING,f=[],g,h=0,k=sjcl.codec.base32.B,l=0,n,m="base32";b&&(k=sjcl.codec.base32.X,m="base32hex");for(g=0;g<a.length;g++){n=k.indexOf(a.charAt(g));if(0>n){if(!b)try{return sjcl.codec.base32hex.toBits(a)}catch(p){}throw new sjcl.exception.invalid("this isn't "+m+"!");}h>e?(h-=e,f.push(l^n>>>h),l=n<<c-h):(h+=d,l^=n<<c-h)}h&56&&f.push(sjcl.bitArray.partial(h&56,l,1));return f}};
sjcl.codec.base32hex={fromBits:function(a,b){return sjcl.codec.base32.fromBits(a,b,1)},toBits:function(a){return sjcl.codec.base32.toBits(a,1)}};
sjcl.codec.base64={B:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",fromBits:function(a,b,c){var d="",e=0,f=sjcl.codec.base64.B,g=0,h=sjcl.bitArray.bitLength(a);c&&(f=f.substr(0,62)+"-_");for(c=0;6*d.length<h;)d+=f.charAt((g^a[c]>>>e)>>>26),6>e?(g=a[c]<<6-e,e+=26,c++):(g<<=6,e-=6);for(;d.length&3&&!b;)d+="=";return d},toBits:function(a,b){a=a.replace(/\s|=/g,"");var c=[],d,e=0,f=sjcl.codec.base64.B,g=0,h;b&&(f=f.substr(0,62)+"-_");for(d=0;d<a.length;d++){h=f.indexOf(a.charAt(d));
if(0>h)throw new sjcl.exception.invalid("this isn't base64!");26<e?(e-=26,c.push(g^h>>>e),g=h<<32-e):(e+=6,g^=h<<32-e)}e&56&&c.push(sjcl.bitArray.partial(e&56,g,1));return c}};sjcl.codec.base64url={fromBits:function(a){return sjcl.codec.base64.fromBits(a,1,1)},toBits:function(a){return sjcl.codec.base64.toBits(a,1)}};sjcl.hash.sha256=function(a){this.b[0]||this.O();a?(this.F=a.F.slice(0),this.A=a.A.slice(0),this.l=a.l):this.reset()};sjcl.hash.sha256.hash=function(a){return(new sjcl.hash.sha256).update(a).finalize()};
sjcl.hash.sha256.prototype={blockSize:512,reset:function(){this.F=this.Y.slice(0);this.A=[];this.l=0;return this},update:function(a){"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));var b,c=this.A=sjcl.bitArray.concat(this.A,a);b=this.l;a=this.l=b+sjcl.bitArray.bitLength(a);if(0x1fffffffffffff<a)throw new sjcl.exception.invalid("Cannot hash more than 2^53 - 1 bits");if("undefined"!==typeof Uint32Array){var d=new Uint32Array(c),e=0;for(b=512+b-(512+b&0x1ff);b<=a;b+=512)u(this,d.subarray(16*e,
16*(e+1))),e+=1;c.splice(0,16*e)}else for(b=512+b-(512+b&0x1ff);b<=a;b+=512)u(this,c.splice(0,16));return this},finalize:function(){var a,b=this.A,c=this.F,b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+2;a&15;a++)b.push(0);b.push(Math.floor(this.l/0x100000000));for(b.push(this.l|0);b.length;)u(this,b.splice(0,16));this.reset();return c},Y:[],b:[],O:function(){function a(a){return 0x100000000*(a-Math.floor(a))|0}for(var b=0,c=2,d,e;64>b;c++){e=!0;for(d=2;d*d<=c;d++)if(0===c%d){e=
!1;break}e&&(8>b&&(this.Y[b]=a(Math.pow(c,.5))),this.b[b]=a(Math.pow(c,1/3)),b++)}}};
function u(a,b){var c,d,e,f=a.F,g=a.b,h=f[0],k=f[1],l=f[2],n=f[3],m=f[4],p=f[5],r=f[6],q=f[7];for(c=0;64>c;c++)16>c?d=b[c]:(d=b[c+1&15],e=b[c+14&15],d=b[c&15]=(d>>>7^d>>>18^d>>>3^d<<25^d<<14)+(e>>>17^e>>>19^e>>>10^e<<15^e<<13)+b[c&15]+b[c+9&15]|0),d=d+q+(m>>>6^m>>>11^m>>>25^m<<26^m<<21^m<<7)+(r^m&(p^r))+g[c],q=r,r=p,p=m,m=n+d|0,n=l,l=k,k=h,h=d+(k&l^n&(k^l))+(k>>>2^k>>>13^k>>>22^k<<30^k<<19^k<<10)|0;f[0]=f[0]+h|0;f[1]=f[1]+k|0;f[2]=f[2]+l|0;f[3]=f[3]+n|0;f[4]=f[4]+m|0;f[5]=f[5]+p|0;f[6]=f[6]+r|0;f[7]=
f[7]+q|0}
sjcl.mode.ccm={name:"ccm",G:[],listenProgress:function(a){sjcl.mode.ccm.G.push(a)},unListenProgress:function(a){a=sjcl.mode.ccm.G.indexOf(a);-1<a&&sjcl.mode.ccm.G.splice(a,1)},fa:function(a){var b=sjcl.mode.ccm.G.slice(),c;for(c=0;c<b.length;c+=1)b[c](a)},encrypt:function(a,b,c,d,e){var f,g=b.slice(0),h=sjcl.bitArray,k=h.bitLength(c)/8,l=h.bitLength(g)/8;e=e||64;d=d||[];if(7>k)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(f=2;4>f&&l>>>8*f;f++);f<15-k&&(f=15-k);c=h.clamp(c,
8*(15-f));b=sjcl.mode.ccm.V(a,b,c,d,e,f);g=sjcl.mode.ccm.C(a,g,c,b,e,f);return h.concat(g.data,g.tag)},decrypt:function(a,b,c,d,e){e=e||64;d=d||[];var f=sjcl.bitArray,g=f.bitLength(c)/8,h=f.bitLength(b),k=f.clamp(b,h-e),l=f.bitSlice(b,h-e),h=(h-e)/8;if(7>g)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(b=2;4>b&&h>>>8*b;b++);b<15-g&&(b=15-g);c=f.clamp(c,8*(15-b));k=sjcl.mode.ccm.C(a,k,c,l,e,b);a=sjcl.mode.ccm.V(a,k.data,c,d,e,b);if(!f.equal(k.tag,a))throw new sjcl.exception.corrupt("ccm: tag doesn't match");
return k.data},na:function(a,b,c,d,e,f){var g=[],h=sjcl.bitArray,k=h.i;d=[h.partial(8,(b.length?64:0)|d-2<<2|f-1)];d=h.concat(d,c);d[3]|=e;d=a.encrypt(d);if(b.length)for(c=h.bitLength(b)/8,65279>=c?g=[h.partial(16,c)]:0xffffffff>=c&&(g=h.concat([h.partial(16,65534)],[c])),g=h.concat(g,b),b=0;b<g.length;b+=4)d=a.encrypt(k(d,g.slice(b,b+4).concat([0,0,0])));return d},V:function(a,b,c,d,e,f){var g=sjcl.bitArray,h=g.i;e/=8;if(e%2||4>e||16<e)throw new sjcl.exception.invalid("ccm: invalid tag length");
if(0xffffffff<d.length||0xffffffff<b.length)throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data");c=sjcl.mode.ccm.na(a,d,c,e,g.bitLength(b)/8,f);for(d=0;d<b.length;d+=4)c=a.encrypt(h(c,b.slice(d,d+4).concat([0,0,0])));return g.clamp(c,8*e)},C:function(a,b,c,d,e,f){var g,h=sjcl.bitArray;g=h.i;var k=b.length,l=h.bitLength(b),n=k/50,m=n;c=h.concat([h.partial(8,f-1)],c).concat([0,0,0]).slice(0,4);d=h.bitSlice(g(d,a.encrypt(c)),0,e);if(!k)return{tag:d,data:[]};for(g=0;g<k;g+=4)g>n&&(sjcl.mode.ccm.fa(g/
k),n+=m),c[3]++,e=a.encrypt(c),b[g]^=e[0],b[g+1]^=e[1],b[g+2]^=e[2],b[g+3]^=e[3];return{tag:d,data:h.clamp(b,l)}}};
sjcl.mode.ocb2={name:"ocb2",encrypt:function(a,b,c,d,e,f){if(128!==sjcl.bitArray.bitLength(c))throw new sjcl.exception.invalid("ocb iv must be 128 bits");var g,h=sjcl.mode.ocb2.S,k=sjcl.bitArray,l=k.i,n=[0,0,0,0];c=h(a.encrypt(c));var m,p=[];d=d||[];e=e||64;for(g=0;g+4<b.length;g+=4)m=b.slice(g,g+4),n=l(n,m),p=p.concat(l(c,a.encrypt(l(c,m)))),c=h(c);m=b.slice(g);b=k.bitLength(m);g=a.encrypt(l(c,[0,0,0,b]));m=k.clamp(l(m.concat([0,0,0]),g),b);n=l(n,l(m.concat([0,0,0]),g));n=a.encrypt(l(n,l(c,h(c))));
d.length&&(n=l(n,f?d:sjcl.mode.ocb2.pmac(a,d)));return p.concat(k.concat(m,k.clamp(n,e)))},decrypt:function(a,b,c,d,e,f){if(128!==sjcl.bitArray.bitLength(c))throw new sjcl.exception.invalid("ocb iv must be 128 bits");e=e||64;var g=sjcl.mode.ocb2.S,h=sjcl.bitArray,k=h.i,l=[0,0,0,0],n=g(a.encrypt(c)),m,p,r=sjcl.bitArray.bitLength(b)-e,q=[];d=d||[];for(c=0;c+4<r/32;c+=4)m=k(n,a.decrypt(k(n,b.slice(c,c+4)))),l=k(l,m),q=q.concat(m),n=g(n);p=r-32*c;m=a.encrypt(k(n,[0,0,0,p]));m=k(m,h.clamp(b.slice(c),p).concat([0,
0,0]));l=k(l,m);l=a.encrypt(k(l,k(n,g(n))));d.length&&(l=k(l,f?d:sjcl.mode.ocb2.pmac(a,d)));if(!h.equal(h.clamp(l,e),h.bitSlice(b,r)))throw new sjcl.exception.corrupt("ocb: tag doesn't match");return q.concat(h.clamp(m,p))},pmac:function(a,b){var c,d=sjcl.mode.ocb2.S,e=sjcl.bitArray,f=e.i,g=[0,0,0,0],h=a.encrypt([0,0,0,0]),h=f(h,d(d(h)));for(c=0;c+4<b.length;c+=4)h=d(h),g=f(g,a.encrypt(f(h,b.slice(c,c+4))));c=b.slice(c);128>e.bitLength(c)&&(h=f(h,d(h)),c=e.concat(c,[-2147483648,0,0,0]));g=f(g,c);
return a.encrypt(f(d(f(h,d(h))),g))},S:function(a){return[a[0]<<1^a[1]>>>31,a[1]<<1^a[2]>>>31,a[2]<<1^a[3]>>>31,a[3]<<1^135*(a[0]>>>31)]}};
sjcl.mode.gcm={name:"gcm",encrypt:function(a,b,c,d,e){var f=b.slice(0);b=sjcl.bitArray;d=d||[];a=sjcl.mode.gcm.C(!0,a,f,d,c,e||128);return b.concat(a.data,a.tag)},decrypt:function(a,b,c,d,e){var f=b.slice(0),g=sjcl.bitArray,h=g.bitLength(f);e=e||128;d=d||[];e<=h?(b=g.bitSlice(f,h-e),f=g.bitSlice(f,0,h-e)):(b=f,f=[]);a=sjcl.mode.gcm.C(!1,a,f,d,c,e);if(!g.equal(a.tag,b))throw new sjcl.exception.corrupt("gcm: tag doesn't match");return a.data},ka:function(a,b){var c,d,e,f,g,h=sjcl.bitArray.i;e=[0,0,
0,0];f=b.slice(0);for(c=0;128>c;c++){(d=0!==(a[Math.floor(c/32)]&1<<31-c%32))&&(e=h(e,f));g=0!==(f[3]&1);for(d=3;0<d;d--)f[d]=f[d]>>>1|(f[d-1]&1)<<31;f[0]>>>=1;g&&(f[0]^=-0x1f000000)}return e},j:function(a,b,c){var d,e=c.length;b=b.slice(0);for(d=0;d<e;d+=4)b[0]^=0xffffffff&c[d],b[1]^=0xffffffff&c[d+1],b[2]^=0xffffffff&c[d+2],b[3]^=0xffffffff&c[d+3],b=sjcl.mode.gcm.ka(b,a);return b},C:function(a,b,c,d,e,f){var g,h,k,l,n,m,p,r,q=sjcl.bitArray;m=c.length;p=q.bitLength(c);r=q.bitLength(d);h=q.bitLength(e);
g=b.encrypt([0,0,0,0]);96===h?(e=e.slice(0),e=q.concat(e,[1])):(e=sjcl.mode.gcm.j(g,[0,0,0,0],e),e=sjcl.mode.gcm.j(g,e,[0,0,Math.floor(h/0x100000000),h&0xffffffff]));h=sjcl.mode.gcm.j(g,[0,0,0,0],d);n=e.slice(0);d=h.slice(0);a||(d=sjcl.mode.gcm.j(g,h,c));for(l=0;l<m;l+=4)n[3]++,k=b.encrypt(n),c[l]^=k[0],c[l+1]^=k[1],c[l+2]^=k[2],c[l+3]^=k[3];c=q.clamp(c,p);a&&(d=sjcl.mode.gcm.j(g,h,c));a=[Math.floor(r/0x100000000),r&0xffffffff,Math.floor(p/0x100000000),p&0xffffffff];d=sjcl.mode.gcm.j(g,d,a);k=b.encrypt(e);
d[0]^=k[0];d[1]^=k[1];d[2]^=k[2];d[3]^=k[3];return{tag:q.bitSlice(d,0,f),data:c}}};sjcl.misc.hmac=function(a,b){this.W=b=b||sjcl.hash.sha256;var c=[[],[]],d,e=b.prototype.blockSize/32;this.w=[new b,new b];a.length>e&&(a=b.hash(a));for(d=0;d<e;d++)c[0][d]=a[d]^909522486,c[1][d]=a[d]^1549556828;this.w[0].update(c[0]);this.w[1].update(c[1]);this.R=new b(this.w[0])};
sjcl.misc.hmac.prototype.encrypt=sjcl.misc.hmac.prototype.mac=function(a){if(this.aa)throw new sjcl.exception.invalid("encrypt on already updated hmac called!");this.update(a);return this.digest(a)};sjcl.misc.hmac.prototype.reset=function(){this.R=new this.W(this.w[0]);this.aa=!1};sjcl.misc.hmac.prototype.update=function(a){this.aa=!0;this.R.update(a)};sjcl.misc.hmac.prototype.digest=function(){var a=this.R.finalize(),a=(new this.W(this.w[1])).update(a).finalize();this.reset();return a};
sjcl.misc.pbkdf2=function(a,b,c,d,e){c=c||1E4;if(0>d||0>c)throw new sjcl.exception.invalid("invalid params to pbkdf2");"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));"string"===typeof b&&(b=sjcl.codec.utf8String.toBits(b));e=e||sjcl.misc.hmac;a=new e(a);var f,g,h,k,l=[],n=sjcl.bitArray;for(k=1;32*l.length<(d||1);k++){e=f=a.encrypt(n.concat(b,[k]));for(g=1;g<c;g++)for(f=a.encrypt(f),h=0;h<f.length;h++)e[h]^=f[h];l=l.concat(e)}d&&(l=n.clamp(l,d));return l};
sjcl.prng=function(a){this.c=[new sjcl.hash.sha256];this.m=[0];this.P=0;this.H={};this.N=0;this.U={};this.Z=this.f=this.o=this.ha=0;this.b=[0,0,0,0,0,0,0,0];this.h=[0,0,0,0];this.L=void 0;this.M=a;this.D=!1;this.K={progress:{},seeded:{}};this.u=this.ga=0;this.I=1;this.J=2;this.ca=0x10000;this.T=[0,48,64,96,128,192,0x100,384,512,768,1024];this.da=3E4;this.ba=80};
sjcl.prng.prototype={randomWords:function(a,b){var c=[],d;d=this.isReady(b);var e;if(d===this.u)throw new sjcl.exception.notReady("generator isn't seeded");if(d&this.J){d=!(d&this.I);e=[];var f=0,g;this.Z=e[0]=(new Date).valueOf()+this.da;for(g=0;16>g;g++)e.push(0x100000000*Math.random()|0);for(g=0;g<this.c.length&&(e=e.concat(this.c[g].finalize()),f+=this.m[g],this.m[g]=0,d||!(this.P&1<<g));g++);this.P>=1<<this.c.length&&(this.c.push(new sjcl.hash.sha256),this.m.push(0));this.f-=f;f>this.o&&(this.o=
f);this.P++;this.b=sjcl.hash.sha256.hash(this.b.concat(e));this.L=new sjcl.cipher.aes(this.b);for(d=0;4>d&&(this.h[d]=this.h[d]+1|0,!this.h[d]);d++);}for(d=0;d<a;d+=4)0===(d+1)%this.ca&&y(this),e=z(this),c.push(e[0],e[1],e[2],e[3]);y(this);return c.slice(0,a)},setDefaultParanoia:function(a,b){if(0===a&&"Setting paranoia=0 will ruin your security; use it only for testing"!==b)throw new sjcl.exception.invalid("Setting paranoia=0 will ruin your security; use it only for testing");this.M=a},addEntropy:function(a,
b,c){c=c||"user";var d,e,f=(new Date).valueOf(),g=this.H[c],h=this.isReady(),k=0;d=this.U[c];void 0===d&&(d=this.U[c]=this.ha++);void 0===g&&(g=this.H[c]=0);this.H[c]=(this.H[c]+1)%this.c.length;switch(typeof a){case "number":void 0===b&&(b=1);this.c[g].update([d,this.N++,1,b,f,1,a|0]);break;case "object":c=Object.prototype.toString.call(a);if("[object Uint32Array]"===c){e=[];for(c=0;c<a.length;c++)e.push(a[c]);a=e}else for("[object Array]"!==c&&(k=1),c=0;c<a.length&&!k;c++)"number"!==typeof a[c]&&
(k=1);if(!k){if(void 0===b)for(c=b=0;c<a.length;c++)for(e=a[c];0<e;)b++,e=e>>>1;this.c[g].update([d,this.N++,2,b,f,a.length].concat(a))}break;case "string":void 0===b&&(b=a.length);this.c[g].update([d,this.N++,3,b,f,a.length]);this.c[g].update(a);break;default:k=1}if(k)throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string");this.m[g]+=b;this.f+=b;h===this.u&&(this.isReady()!==this.u&&A("seeded",Math.max(this.o,this.f)),A("progress",this.getProgress()))},
isReady:function(a){a=this.T[void 0!==a?a:this.M];return this.o&&this.o>=a?this.m[0]>this.ba&&(new Date).valueOf()>this.Z?this.J|this.I:this.I:this.f>=a?this.J|this.u:this.u},getProgress:function(a){a=this.T[a?a:this.M];return this.o>=a?1:this.f>a?1:this.f/a},startCollectors:function(){if(!this.D){this.a={loadTimeCollector:B(this,this.ma),mouseCollector:B(this,this.oa),keyboardCollector:B(this,this.la),accelerometerCollector:B(this,this.ea),touchCollector:B(this,this.qa)};if(window.addEventListener)window.addEventListener("load",
this.a.loadTimeCollector,!1),window.addEventListener("mousemove",this.a.mouseCollector,!1),window.addEventListener("keypress",this.a.keyboardCollector,!1),window.addEventListener("devicemotion",this.a.accelerometerCollector,!1),window.addEventListener("touchmove",this.a.touchCollector,!1);else if(document.attachEvent)document.attachEvent("onload",this.a.loadTimeCollector),document.attachEvent("onmousemove",this.a.mouseCollector),document.attachEvent("keypress",this.a.keyboardCollector);else throw new sjcl.exception.bug("can't attach event");
this.D=!0}},stopCollectors:function(){this.D&&(window.removeEventListener?(window.removeEventListener("load",this.a.loadTimeCollector,!1),window.removeEventListener("mousemove",this.a.mouseCollector,!1),window.removeEventListener("keypress",this.a.keyboardCollector,!1),window.removeEventListener("devicemotion",this.a.accelerometerCollector,!1),window.removeEventListener("touchmove",this.a.touchCollector,!1)):document.detachEvent&&(document.detachEvent("onload",this.a.loadTimeCollector),document.detachEvent("onmousemove",
this.a.mouseCollector),document.detachEvent("keypress",this.a.keyboardCollector)),this.D=!1)},addEventListener:function(a,b){this.K[a][this.ga++]=b},removeEventListener:function(a,b){var c,d,e=this.K[a],f=[];for(d in e)e.hasOwnProperty(d)&&e[d]===b&&f.push(d);for(c=0;c<f.length;c++)d=f[c],delete e[d]},la:function(){C(this,1)},oa:function(a){var b,c;try{b=a.x||a.clientX||a.offsetX||0,c=a.y||a.clientY||a.offsetY||0}catch(d){c=b=0}0!=b&&0!=c&&this.addEntropy([b,c],2,"mouse");C(this,0)},qa:function(a){a=
a.touches[0]||a.changedTouches[0];this.addEntropy([a.pageX||a.clientX,a.pageY||a.clientY],1,"touch");C(this,0)},ma:function(){C(this,2)},ea:function(a){a=a.accelerationIncludingGravity.x||a.accelerationIncludingGravity.y||a.accelerationIncludingGravity.z;if(window.orientation){var b=window.orientation;"number"===typeof b&&this.addEntropy(b,1,"accelerometer")}a&&this.addEntropy(a,2,"accelerometer");C(this,0)}};
function A(a,b){var c,d=sjcl.random.K[a],e=[];for(c in d)d.hasOwnProperty(c)&&e.push(d[c]);for(c=0;c<e.length;c++)e[c](b)}function C(a,b){"undefined"!==typeof window&&window.performance&&"function"===typeof window.performance.now?a.addEntropy(window.performance.now(),b,"loadtime"):a.addEntropy((new Date).valueOf(),b,"loadtime")}function y(a){a.b=z(a).concat(z(a));a.L=new sjcl.cipher.aes(a.b)}function z(a){for(var b=0;4>b&&(a.h[b]=a.h[b]+1|0,!a.h[b]);b++);return a.L.encrypt(a.h)}
function B(a,b){return function(){b.apply(a,arguments)}}sjcl.random=new sjcl.prng(6);
a:try{var D,E,F,G;if(G="undefined"!==typeof module&&module.exports){var H;try{H=require("crypto")}catch(a){H=null}G=E=H}if(G&&E.randomBytes)D=E.randomBytes(128),D=new Uint32Array((new Uint8Array(D)).buffer),sjcl.random.addEntropy(D,1024,"crypto['randomBytes']");else if("undefined"!==typeof window&&"undefined"!==typeof Uint32Array){F=new Uint32Array(32);if(window.crypto&&window.crypto.getRandomValues)window.crypto.getRandomValues(F);else if(window.msCrypto&&window.msCrypto.getRandomValues)window.msCrypto.getRandomValues(F);
else break a;sjcl.random.addEntropy(F,1024,"crypto['getRandomValues']")}}catch(a){"undefined"!==typeof window&&window.console&&(console.log("There was an error collecting entropy from the browser:"),console.log(a))}
sjcl.json={defaults:{v:1,iter:1E4,ks:128,ts:64,mode:"ccm",adata:"",cipher:"aes"},ja:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json,f=e.g({iv:sjcl.random.randomWords(4,0)},e.defaults),g;e.g(f,c);c=f.adata;"string"===typeof f.salt&&(f.salt=sjcl.codec.base64.toBits(f.salt));"string"===typeof f.iv&&(f.iv=sjcl.codec.base64.toBits(f.iv));if(!sjcl.mode[f.mode]||!sjcl.cipher[f.cipher]||"string"===typeof a&&100>=f.iter||64!==f.ts&&96!==f.ts&&128!==f.ts||128!==f.ks&&192!==f.ks&&0x100!==f.ks||2>f.iv.length||
4<f.iv.length)throw new sjcl.exception.invalid("json encrypt: invalid parameters");"string"===typeof a?(g=sjcl.misc.cachedPbkdf2(a,f),a=g.key.slice(0,f.ks/32),f.salt=g.salt):sjcl.ecc&&a instanceof sjcl.ecc.elGamal.publicKey&&(g=a.kem(),f.kemtag=g.tag,a=g.key.slice(0,f.ks/32));"string"===typeof b&&(b=sjcl.codec.utf8String.toBits(b));"string"===typeof c&&(f.adata=c=sjcl.codec.utf8String.toBits(c));g=new sjcl.cipher[f.cipher](a);e.g(d,f);d.key=a;f.ct="ccm"===f.mode&&sjcl.arrayBuffer&&sjcl.arrayBuffer.ccm&&
b instanceof ArrayBuffer?sjcl.arrayBuffer.ccm.encrypt(g,b,f.iv,c,f.ts):sjcl.mode[f.mode].encrypt(g,b,f.iv,c,f.ts);return f},encrypt:function(a,b,c,d){var e=sjcl.json,f=e.ja.apply(e,arguments);return e.encode(f)},ia:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json;b=e.g(e.g(e.g({},e.defaults),b),c,!0);var f,g;f=b.adata;"string"===typeof b.salt&&(b.salt=sjcl.codec.base64.toBits(b.salt));"string"===typeof b.iv&&(b.iv=sjcl.codec.base64.toBits(b.iv));if(!sjcl.mode[b.mode]||!sjcl.cipher[b.cipher]||"string"===
typeof a&&100>=b.iter||64!==b.ts&&96!==b.ts&&128!==b.ts||128!==b.ks&&192!==b.ks&&0x100!==b.ks||!b.iv||2>b.iv.length||4<b.iv.length)throw new sjcl.exception.invalid("json decrypt: invalid parameters");"string"===typeof a?(g=sjcl.misc.cachedPbkdf2(a,b),a=g.key.slice(0,b.ks/32),b.salt=g.salt):sjcl.ecc&&a instanceof sjcl.ecc.elGamal.secretKey&&(a=a.unkem(sjcl.codec.base64.toBits(b.kemtag)).slice(0,b.ks/32));"string"===typeof f&&(f=sjcl.codec.utf8String.toBits(f));g=new sjcl.cipher[b.cipher](a);f="ccm"===
b.mode&&sjcl.arrayBuffer&&sjcl.arrayBuffer.ccm&&b.ct instanceof ArrayBuffer?sjcl.arrayBuffer.ccm.decrypt(g,b.ct,b.iv,b.tag,f,b.ts):sjcl.mode[b.mode].decrypt(g,b.ct,b.iv,f,b.ts);e.g(d,b);d.key=a;return 1===c.raw?f:sjcl.codec.utf8String.fromBits(f)},decrypt:function(a,b,c,d){var e=sjcl.json;return e.ia(a,e.decode(b),c,d)},encode:function(a){var b,c="{",d="";for(b in a)if(a.hasOwnProperty(b)){if(!b.match(/^[a-z0-9]+$/i))throw new sjcl.exception.invalid("json encode: invalid property name");c+=d+'"'+
b+'":';d=",";switch(typeof a[b]){case "number":case "boolean":c+=a[b];break;case "string":c+='"'+escape(a[b])+'"';break;case "object":c+='"'+sjcl.codec.base64.fromBits(a[b],0)+'"';break;default:throw new sjcl.exception.bug("json encode: unsupported type");}}return c+"}"},decode:function(a){a=a.replace(/\s/g,"");if(!a.match(/^\{.*\}$/))throw new sjcl.exception.invalid("json decode: this isn't json!");a=a.replace(/^\{|\}$/g,"").split(/,/);var b={},c,d;for(c=0;c<a.length;c++){if(!(d=a[c].match(/^\s*(?:(["']?)([a-z][a-z0-9]*)\1)\s*:\s*(?:(-?\d+)|"([a-z0-9+\/%*_.@=\-]*)"|(true|false))$/i)))throw new sjcl.exception.invalid("json decode: this isn't json!");
null!=d[3]?b[d[2]]=parseInt(d[3],10):null!=d[4]?b[d[2]]=d[2].match(/^(ct|adata|salt|iv)$/)?sjcl.codec.base64.toBits(d[4]):unescape(d[4]):null!=d[5]&&(b[d[2]]="true"===d[5])}return b},g:function(a,b,c){void 0===a&&(a={});if(void 0===b)return a;for(var d in b)if(b.hasOwnProperty(d)){if(c&&void 0!==a[d]&&a[d]!==b[d])throw new sjcl.exception.invalid("required parameter overridden");a[d]=b[d]}return a},sa:function(a,b){var c={},d;for(d in a)a.hasOwnProperty(d)&&a[d]!==b[d]&&(c[d]=a[d]);return c},ra:function(a,
b){var c={},d;for(d=0;d<b.length;d++)void 0!==a[b[d]]&&(c[b[d]]=a[b[d]]);return c}};sjcl.encrypt=sjcl.json.encrypt;sjcl.decrypt=sjcl.json.decrypt;sjcl.misc.pa={};sjcl.misc.cachedPbkdf2=function(a,b){var c=sjcl.misc.pa,d;b=b||{};d=b.iter||1E3;c=c[a]=c[a]||{};d=c[d]=c[d]||{firstSalt:b.salt&&b.salt.length?b.salt.slice(0):sjcl.random.randomWords(2,0)};c=void 0===b.salt?d.firstSalt:b.salt;d[c]=d[c]||sjcl.misc.pbkdf2(a,c,b.iter);return{key:d[c].slice(0),salt:c.slice(0)}};
"undefined"!==typeof module&&module.exports&&(module.exports=sjcl);"function"===typeof define&&define([],function(){return sjcl});

window.sjcl = sjcl;
//}}}
//{{{
// make plugin work with MainTiddlyServer and TW 2.8.1+
config.options.chkAvoidGranulatedSaving = true;

config.shadowTiddlers.SideBarOptions = config.shadowTiddlers.SideBarOptions.replace(
/<<saveChanges>>/, "<<unlock>><<setPassword>><<saveChanges>>");
config.shadowTiddlers.GettingStarted +=
"\n\n<<ifLocked 'This TiddlyWiki use EncryptedVaultPlugin. " +
"To load protected content click on'>><<unlock>>" +
"<<ifUnlocked 'This TiddlyWiki use EncryptedVaultPlugin. " +
"To set or change password click on'>><<setPassword>>";

merge(config.messages, {
vaultCreationInfo: "The encrypted vault has been created",
passwordSet: "🔒 Successfully set password",
passwordUnset: "🔓 Successfully unset password",
purgeConfirm: "Purge the encrypted vault ?\n\nAll unlocked content will be lost.",
vaultPurgedInfo: "All contents have been purged from encrypted vault.\nPassword has been blanked.\nYou must save once to apply this changes.",
vaultEncryptedInfo: "Saving with encryption",
vaultUnchangedInfo: "No changes in Encrypted vault",
noLockedVaultNoPurge: "No locked encrypted vault, nothing to purge.",
emptyVaultInfo: "Saving without encryption",
saveWithLockedVaultConfirm: "Encrypted vault is locked. No changes will apply inside.\n\nAre you sure ?",
confirmOverload: "This following tiddler already exists in system store. Overload ?\nOK : the encrypted version will replace the system store version\nCancel : the system store version will replace the encrypted version"
});

config.extensions = config.extensions || {}
config.extensions.vault = {
// TODO: encapsulate sjcl from global context?
callSjcl: function(method, inputText, password) {
if(!password) return

try {
var outputText = window.sjcl[method](password, inputText)
} catch(ex) {
console.log("Crypto error: " + ex)
return null
}
return outputText
},
// this constant string allows to distinguish whether some content
// is encrypted with the algorithm used here
// TODO: for updating the plugin in some TWs, detect old prefix (Cryptomx@) and warn
prefix: "Sjcl@",
encrypt: function(src, password) {
if(!password) return src
return this.prefix + this.callSjcl("encrypt", src, password)
},
// if a wrong password is used, returns src as is
decrypt: function(src, password) {
var res = this.callSjcl("decrypt", src.substr(this.prefix.length), password)
return res || src
},
isEncrypted: function(src) {
return src.substr(0, this.prefix.length) == this.prefix
},

// these should not be found by indexOf() of this source;
// as < is encoded as &lt;, this won't happen anyway
vaultAreaId: 'vaultArea',
startSaveVaultArea: '<div id="vaultArea">',
endSaveVaultArea: '</div>',
postVaultAreaMarker: '<!--POST-VAULTAREA-->',

// TODO: use this instead of createVaultArea, remove .. bits in updateOriginal
// TODO: get rid of side-effects (alerts, displayMessage)
addOrUpdateVaultArea: function(twHtml) {
var posVault = this.locateVaultArea(twHtml)
if(!posVault) {
twHtml = this.createVaultArea(twHtml)
alert(config.messages.vaultCreationInfo);
posVault = this.locateVaultArea(twHtml)
if(!posVault) alertAndThrow(
config.messages.invalidFileError.format([localPath]));
}

var newVaultContent = !this.password ? "" :
this.encrypt(store.allEncryptedTiddlersAsHtml(), this.password)
var updatedHtml = twHtml.substr(0, posVault[0] + this.startSaveVaultArea.length) +
convertUnicodeToUTF8(newVaultContent) +
twHtml.substr(posVault[1])
displayMessage(config.messages[this.password ?
'vaultEncryptedInfo' : 'emptyVaultInfo'])
return updatedHtml
},
createVaultArea: function(original) {
var revised = original.replace(/<!--POST-SHADOWAREA-->/,
'<!--POST-SHADOWAREA-->\n' +
this.startSaveVaultArea + this.endSaveVaultArea + '\n' +
this.postVaultAreaMarker)

// TODO: insert into styleArea instead, use CSS comment as a marker
// or even better add to PRE-HEAD
var vaultStylesMarker = '<!--PRE-VAULTSTYLE-START-->'
if(revised.indexOf(vaultStylesMarker) < 0) {
var vaultStylesHtml = vaultStylesMarker + '\n<style type="text/css">\n' +
'#'+ this.vaultAreaId +' { display: none; }' +
'\n</style>\n<!--PRE-VAULTSTYLE-END-->\n'
revised = revised.replace(/<!--POST-HEAD-START-->/,
vaultStylesHtml + '<!--POST-HEAD-START-->')
}

return revised
},
// adapted from locateStoreArea
locateVaultArea: function(original) {
if(!original) return null

// the vaultArea div should be just before the storeArea div
var posOpeningDiv = original.indexOf(this.startSaveVaultArea)
var limitClosingDiv = original.indexOf(this.postVaultAreaMarker)
// startSaveArea is globally available (should be deprecated though)
if(limitClosingDiv == -1)
limitClosingDiv = original.indexOf(startSaveArea)
var posClosingDiv = original.lastIndexOf(this.endSaveVaultArea, limitClosingDiv)

return (posOpeningDiv == -1 || posClosingDiv == -1) ? null :
[posOpeningDiv, posClosingDiv];
},

tagDontEncrypt: "unencrypted",
tagForceEncrypt: "forceEncryption",
getUnencryptedArray: function() {
var unencryptedList = store.fetchTiddler("ListUnencrypted")
if(!unencryptedList) return
var filter = unencryptedList.text
return store.filterTiddlers(filter)
},
// this is used to avoid quadratic complexity (filtering N tiddlers per each N tiddlers)
unencryptedCacheMap: null,
populateUnencryptedCacheMap: function() {
var unencryptedArray = this.getUnencryptedArray() || []
this.unencryptedCacheMap = {}
for(var i = 0; i < unencryptedArray.length; i++) {
var title = unencryptedArray[i].title
this.unencryptedCacheMap[title] = true
}
},
clearnUnencryptedCacheMap: function() {
this.unencryptedCacheMap = null
},
shouldEncryptTiddler: function(tiddler) {
if(tiddler.isTagged(this.tagForceEncrypt)) return true

if(store.isShadowTiddler([tiddler.title])
|| tiddler.title === "ListUnencrypted"
|| tiddler.isTagged("systemConfig")
|| tiddler.isTagged(this.tagDontEncrypt)
) return false

if(this.unencryptedCacheMap) {
return !this.unencryptedCacheMap[tiddler.title]
} else {
var unencryptedArray = this.getUnencryptedArray()
if(unencryptedArray && unencryptedArray.indexOf(tiddler) !== -1) return false
/*var unencryptedList = store.fetchTiddler("ListUnencrypted")
if(unencryptedList) {
var filter = unencryptedList.text
var tids = store.filterTiddlers(filter)
if(tids.indexOf(tiddler) !== -1) return false
}*/
}

return true
},

// TODO: review terms (locked, loaded, ..) – should be consistent
getVaultContent: function() {
var el = document.getElementById(this.vaultAreaId)
return el ? el.innerHTML : null
},
// TODO: remove state; may be move some sections above to ~submodules instead
// loaded: falsy by default (we don't set it so that installing twice won't hurt)
isLocked: function() {

if(this.loaded) return false
var vaultContent = this.getVaultContent()
return vaultContent === null || this.isEncrypted(vaultContent)
},
existsAndIsLocked: function() {
return this.getVaultContent() !== null &&
this.isLocked()
},
// TODO: fix lingo (missing, prompt); review
// returns a boolean indicating success
load: function() {
if (!this.isLocked()) {
// vaultAlreadyUnlockedWarning is missing even in the original plugin!
alert(config.messages.vaultAlreadyUnlockedWarning);

return false;
}

var vaultContent = this.getVaultContent()
if(vaultContent === null) return false

var pwd = this.password || "";
while(this.isEncrypted(vaultContent) && (pwd != null)) {
if(pwd) vaultContent = this.decrypt(vaultContent, pwd);
if(this.isEncrypted(vaultContent))
pwd = prompt("Enter a password", pwd);
}
if(pwd != null) this.password = pwd;
if(this.isEncrypted(vaultContent)) return false;

var wasDirty = store.isDirty();
if(vaultContent) {
var e = document.createElement("div");
e.innerHTML = vaultContent;
store.getLoader().loadTiddlers(store, e.childNodes);
}
this.loaded = true;
refreshAll();
story.refreshAllTiddlers();
store.setDirty(wasDirty);
return true
},

// TODO: expose password setter, but hide direct access to password, if possible
// password: falsy by default

// TODO: can we implement a method "purge" instead? review current logic:
// seems to have multiple flaws (one is: it's never restored to false!)
// Make sure installing twice won't hurt
shouldPurge: false,

// save for decorating; avoid problems if this is installed twice
originals: config.extensions.vault ? config.extensions.vault.originals : {
updateOriginal: updateOriginal,
LoaderBase_loadTiddler: LoaderBase.prototype.loadTiddler,
saveChanges: saveChanges,
Tiddler_doNotSave: Tiddler.prototype.doNotSave
},
doNotSaveMode: 'encrypted'
}

// TODO: try `this` instead of `store`; why `TiddlyWiki.prototype.allTiddlersAsHtml` uses that?
//TiddlyWiki.prototype.allUnencryptedTiddlersAsHtml = function() {
Tiddler.prototype.doNotSave = function() {
var shouldEncrypt = config.extensions.vault.shouldEncryptTiddler(this)
var mode = config.extensions.vault.doNotSaveMode
if(shouldEncrypt && mode == "encrypted" || !shouldEncrypt && mode == "unencrypted") return true
return config.extensions.vault.originals.Tiddler_doNotSave.apply(this, arguments)
}

/* var result = this.getSaver().externalize(store)

Tiddler.prototype.doNotSave = config.extensions.vault.originals.Tiddler_doNotSave

return result
}*/

TiddlyWiki.prototype.allEncryptedTiddlersAsHtml = function() {
var prevMode = config.extensions.vault.doNotSaveMode
config.extensions.vault.doNotSaveMode = "unencrypted"
// Tiddler.prototype.doNotSave = function() {
// if(!config.extensions.vault.shouldEncryptTiddler(this)) return true
// return config.extensions.vault.originals.Tiddler_doNotSave.apply(this, arguments)
// }

var result = this.getSaver().externalize(store)

// Tiddler.prototype.doNotSave = config.extensions.vault.originals.Tiddler_doNotSave
config.extensions.vault.doNotSaveMode = prevMode

return result
}

// TODO: review all decorations; may be move (encapsulate) some bits to ceVault methods
// decorate
window.updateOriginal = function(original, posDiv) {
var ceVault = config.extensions.vault
var vaultIsUpdatable = !ceVault.locateVaultArea(original) ||
!ceVault.isLocked() || ceVault.shouldPurge

ceVault.populateUnencryptedCacheMap()
var orig_allTiddlersAsHtml = store.allTiddlersAsHtml
//# or decorate prototype?
/* store.allTiddlersAsHtml = function() {
return convertUnicodeToUTF8((vaultIsUpdatable && ceVault.password) ?
this.allUnencryptedTiddlersAsHtml() :
orig_allTiddlersAsHtml.apply(this, arguments))
}
*/
var revised = ceVault.originals.updateOriginal.apply(this, arguments)
if(vaultIsUpdatable) {
// reports results via alert and/or displayMessage
revised = ceVault.addOrUpdateVaultArea(revised)
} else
displayMessage(config.messages.vaultUnchangedInfo)

store.allTiddlersAsHtml = orig_allTiddlersAsHtml
ceVault.clearnUnencryptedCacheMap()

return revised
}

// decorate
LoaderBase.prototype.loadTiddler = function(store, node, tiddlers) {
var title = this.getTitle(store, node);

if(store.getTiddler(title) && !confirm(config.messages.confirmOverload +"\n\n"+ title))
return;
return config.extensions.vault.originals.LoaderBase_loadTiddler.apply(this, arguments)
}

// decorate
window.saveChanges = function(onlyIfDirty, tiddlers) {
var ceVault = config.extensions.vault
if(ceVault.shouldPurge || !ceVault.existsAndIsLocked() ||
confirm(config.messages.saveWithLockedVaultConfirm))
ceVault.originals.saveChanges.apply(this, arguments);
}

var shadowName = "StyleSheetVault"
if(!config.shadowTiddlers[shadowName]) {
config.shadowTiddlers[shadowName] = 'input { max-width: 100%; }'
store.addNotification(shadowName, refreshStyles)
store.addNotification("ColorPalette", function(smth, doc) { refreshStyles(shadowName, doc) })
}

// TODO: move defaults to lingo
config.macros.unlock = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var form = createTiddlyElement(place, 'form')
jQuery(form).attr({ refresh: "macro", macroName: macroName }).data({
label:   params[0] || "unlock vault",
tooltip: params[1] || "unlock encrypted vault",
openTiddlers: params[2] || "",
closeTiddlers: params[3] || "",
})
this.refresh(form)
},
refresh: function(form) {
var params = jQuery(form).data()
jQuery(form).empty()
var macro = this
var ceVault = config.extensions.vault

//# or may be show something more helpful
if(!ceVault.existsAndIsLocked()) return;

var input = createTiddlyElement(form, 'input', null, null, null, {
type: 'password'
})
input.focus()

jQuery(input).on('keydown', function(event) {
if(event.key !== 'Enter') return
macro.unlockAndOpen(input.value,
params.openTiddlers, params.closeTiddlers)
return false
})
// TODO: explain they can type and press "enter"

createTiddlyButton(form, params.label, params.tooltip, function() {
macro.unlockAndOpen(input.value,
params.openTiddlers, params.closeTiddlers)
return false
})
},
unlockAndOpen: function(newPassword, openTiddlersFilter, closeTiddlersFilter) {
var ceVault = config.extensions.vault
if(newPassword) ceVault.password = newPassword

if(ceVault.load()) {
if(closeTiddlersFilter) {
var tiddlers = store.filterTiddlers(closeTiddlersFilter)
for(var i = 0; i < tiddlers.length; i++) {
if(!story.isDirty(tiddlers[i].title))
story.closeTiddler(tiddlers[i].title)
}
}
if(openTiddlersFilter) {
var tiddlers = store.filterTiddlers(openTiddlersFilter)
for(var i = 0; i < tiddlers.length; i++)
story.displayTiddler("bottom", tiddlers[i].title)
}
}
}
}

// TODO: move defaults to lingo
config.macros.setPassword = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var form = createTiddlyElement(place, 'form')
jQuery(form).attr({ refresh: "macro", macroName: macroName }).data({
label:   params[0] || "set password",
tooltip: params[1] || "Set password for encrypted vault"
})
this.refresh(form)
},
refresh: function(form) {
var params = jQuery(form).data()
jQuery(form).empty()

if(config.extensions.vault.isLocked()) return
createTiddlyButton(form, params.label, params.tooltip, this.onClick)
},
onClick: function(event) {
var form = event.target.parentElement
jQuery(form).empty()
var refreshForm = function() { config.macros.setPassword.refresh(form) }

var input = createTiddlyElement(form, 'input', null, null, null, {
type: 'password'
})
input.focus()

var ceVault = config.extensions.vault
jQuery(input).on('keydown', function(event) {
if(event.key === 'Escape') return refreshForm()

if(event.key !== 'Enter') return
ceVault.password = input.value

refreshForm()
displayMessage(ceVault.password ?
config.messages.passwordSet :
config.messages.passwordUnset)
return false
})
.on('blur', refreshForm)
// TODO: explain they have to type and press "enter" and/or add a button for that
// TODO: add also way to cancel (в†’ refresh form)

return false;
}
}

config.macros.purge = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var label   = params[0] || "purge vault";
var tooltip = params[1] || "Delete locked vault";
var ceVault = config.extensions.vault
if (ceVault.existsAndIsLocked())
createTiddlyButton(place, label, tooltip, this.onClick);
},
onClick: function() {
var ceVault = config.extensions.vault
if (!ceVault.isLocked())
alert(config.messages.noLockedVaultNoPurge);
else
if(confirm(config.messages.purgeConfirm)) {
ceVault.shouldPurge = true;
alert(config.messages.vaultPurgedInfo);
}
return false;
}
}

config.macros.ifLocked = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
if(config.extensions.vault.existsAndIsLocked())
wikify(params[0], place, null, tiddler);
}
}

config.macros.ifUnlocked = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
if(config.extensions.vault.isLocked()) return;
wikify(params[0], place, null, tiddler);
}
}
//}}}

среда, 25 декабря 2024 г. в 18:47:14 UTC+3, samp...@gmail.com:

sam paul

unread,
Feb 8, 2025, 11:32:44 PMFeb 8
to TiddlyWikiClassic
Hello Yakov.
Thanks for the update.
I test the new plugin. After I load it and tag it and save it, when I enter the password 'test', it keeps popping up windows saying 
"enter the password"
"test"

Best.

sam paul

unread,
Mar 1, 2025, 3:03:53 PMMar 1
to TiddlyWikiClassic
Hello Yakov,

Below is the tiddlywiki that has almost all the plugins as the one I am using. The encryption is still not working properly. It seems that it stores an extra unencrypted copy of the encrypted one. The unencrypted one is called 'base.html'. The encrypted one is named as 'base_encrypted.html'. The password is '123456'.
I appreciate your help.

sam paul

unread,
Mar 1, 2025, 3:07:01 PMMar 1
to TiddlyWikiClassic
The file is too large to be uploaded to google groups. Here is the google drive link to 'base.html' and 'base_encrypted.html': https://drive.google.com/drive/folders/1g_I5YbJvTuj0BtCLDDyNozCA_wv4OWNh?usp=drive_link .

Best.

Yakov

unread,
Mar 7, 2025, 12:35:43 PMMar 7
to TiddlyWikiClassic
Hello Sam,

great job, this definitely helped! Try disabling TemporaryTiddlersPlugin, it overrides SaverBase.prototype.externalize instead of decorating Tiddler.prototype.doNotSave, that's why TiddlyWiki.prototype.allUnencryptedTiddlersAsHtml doesn't work as expected (i.e. saves encrypted tiddlers as well). If you need TemporaryTiddlersPlugin, I can rewrite it so that it doesn't break compatibility with other plugins.

Best regards,
Yakov

суббота, 1 марта 2025 г. в 23:07:01 UTC+3, samp...@gmail.com:

Yakov

unread,
May 22, 2025, 1:58:07 PMMay 22
to TiddlyWikiClassic
Hello Sam, I've made some more testing and fixes and I think v2 is ready to use. I haven't updated docs yet, so it's not published in the main repository, but if you try it and works for you, let me know. By the way, have you tried the fixed suggested above?

/***
|Name        |EncryptionPlugin|
|Description |Adds AES encryption and password protection and several macros to work with them|
|Version     |2.0.3|

|Source      |https://github.com/YakovL/TiddlyWiki_EncryptionPlugin/blob/master/EncryptionPlugin.js|
|Author      |Yakov Litvin|
|Forked from |[[EncryptedVaultPlugin|http://visualtw.ouvaton.org/VisualTW.html#EncryptedVaultPlugin]] by Pascal Collin (now available at [[GitHub|https://yakovl.github.io/VisualTW2/VisualTW2.html#EncryptedVaultPlugin]] or [[in web archive|https://web.archive.org/web/20160130130224/http://visualtw.ouvaton.org/VisualTW.html#EncryptedVaultPlugin]]); versions up to 1.7.1 were pre-released as ~NewEncryptedVaultPlugin|
|Browser     |The plugin is supposed to work in any modern browser, but it is recommended to create a backup of your TW before trying it with your setup|
|License|[[BSD open source license|License]]|
!Demo
--Use <<unlock>> button on a protected wiki. By example: http://visualtw.ouvaton.org/demo/EncryptedVaultPlugin.html--
!Installation
Unlike v1.x, v2.x only requires installing as usual (import/copy the plugin with the {{{systemConfig}}} tag, save and reload).
!Upgrading from 1.x
Although it works smoothly, it is recommended to create a backup before upgrading from 1.x.
After upgrading, manual removal of the old vault is recommended: open TW with a text editor, find {{{<!--POST-SHADOWAREA-->}}}, and remove the 2 lines after  it (one starting with {{{<div id="vaultArea">}}} and the other being {{{<!--POST-VAULTAREA-->}}}).
!Migration from ~EncryptedVaultPlugin
TODO: describe how to and backward compatibility
{{PoG{test thoroughly (including, DefaultTiddlers, first run of the plugin, macros appearence and usage, data format compatibility, including unencrypted, ...; .oO MVP for ...) and update metadata}}}

Compared to ~EncryptedVaultPlugin, this plugin:
* Uses a stonger encryption (AES);
* Fixes various major issues, uses decorators instead of overridings of several core functions;

* Uses a customizable StyleSheetVault shadow for styles;
* Improves the UI for password (doesn't show the typed value, adds autofocus, supports pressing enter to apply).
!Usage

* Use <<unlock>><<setPassword>> button (available by default in SideBarOptions)
* Use a blank password to save unencrypted (disable vault usage)
* Use {{{unencrypted}}} tag to avoid encryption for some tiddler
* Use {{{forceEncryption}}} tag to force some shadow tiddler to be encrypted
* TODO: describe other macros
!Description
* Create an ''encrypted vault'' where all tiddlers are ''password protected''.
* By default, only the system tiddlers (shadow ones and those tagged {{{systemConfig}}}) aren't encrypted. Also, if you create [[ListUnencrypted]] and put a [[filter|classic.tiddlywiki.com#Filters]] there, the filtered tiddlers won't be encrypted, too.
* Even shadow tiddlers (MainMenu, SiteTitle, PageTemplate, StyleSheet, ...) ''can be encrypted''. The shadow version is used until unlocking.
!Configuration
The following macros are available:
* {{{<<unlock ButtonTitle ButtonTooltip OpenTiddlersWhenUnlock CloseTiddlersWhenUnlock>>}}} creates a button to unlock the encrypted vault (all parameters are optional)
* {{{<<setPassword ButtonTitle ButtonTooltip>>}}} if unlocked, creates a button to set the current password (all parameters are optional)
* {{{<<purge ButtonTitle ButtonTooltip>>}}} if locked, creates a button to purge a locked vault, useful for lost password (encrypted content is the deleted)
* {{{<<ifLocked tiddlyText>>}}} displays tiddlyText (wikified) if the vault is locked
* {{{<<ifUnlocked tiddlyText>>}}} displays tiddlyText (wikified) if the vault is unlocked
<<ifLocked "!!!!Lost password ?">><<ifLocked "Click on">> <<purge>><<ifLocked "to delete any content locked in the encrypted vault.">>
return res === null ? src : res

},
isEncrypted: function(src) {
return src.substr(0, this.prefix.length) == this.prefix
},

// these should not be found by indexOf() of this source;
// as < is encoded as &lt;, this won't happen anyway
vaultAreaId: 'vaultArea',
startSaveVaultArea: '<div id="vaultArea">',
endSaveVaultArea: '</div>',
postVaultAreaMarker: '<!--POST-VAULTAREA-->',

// adapted from locateStoreArea
locateVaultArea: function(original) {
if(!original) return null

// the vaultArea div should be just before the storeArea div
var posOpeningDiv = original.indexOf(this.startSaveVaultArea)
var limitClosingDiv = original.indexOf(this.postVaultAreaMarker)
// startSaveArea is globally available (should be deprecated though)
if(limitClosingDiv == -1)
limitClosingDiv = original.indexOf(startSaveArea)
var posClosingDiv = original.lastIndexOf(this.endSaveVaultArea, limitClosingDiv)

return (posOpeningDiv == -1 || posClosingDiv == -1) ? null :
[posOpeningDiv, posClosingDiv];
},

// implementing in-tiddler vault
singleVaultTiddlerName: "EncryptedVault",
updateVaultTiddler: function(encryptedContentOrNull) {
if(encryptedContentOrNull === null) {
// TODO: ask for confirmation? make undo-able?
store.deleteTiddler(this.singleVaultTiddlerName)
return
}

var encryptedContent = encryptedContentOrNull
var vaultTiddler = store.fetchTiddler(this.singleVaultTiddlerName) // fetch existing or create
if(!vaultTiddler) {
vaultTiddler = new Tiddler(this.singleVaultTiddlerName)
vaultTiddler.tags.push(this.tagDontEncrypt)
store.addTiddler(vaultTiddler) // doesn't setDirty, unlike createTiddler
}
vaultTiddler.text = encryptedContent
vaultTiddler.modified = new Date()
// TODO: update modifier? (see core methods that update the fields) something else?
},
getVaultTiddlerContent: function() {
var vaultTiddler = store.fetchTiddler(this.singleVaultTiddlerName)
return vaultTiddler ? vaultTiddler.text : null
// same, but returning null is implicit: return store.getTiddlerText(this.singleVaultTiddlerName)

},

tagDontEncrypt: "unencrypted",
tagForceEncrypt: "forceEncryption",
getUnencryptedArray: function() {
var unencryptedList = store.fetchTiddler("ListUnencrypted")
return !unencryptedList ? [] :
store.filterTiddlers(unencryptedList.text)

},
// this is used to avoid quadratic complexity (filtering N tiddlers per each N tiddlers)
unencryptedCacheMap: null,
populateUnencryptedCacheMap: function() {
var unencryptedArray = this.getUnencryptedArray()
this.unencryptedCacheMap = {}
for(var i = 0; i < unencryptedArray.length; i++) {
var title = unencryptedArray[i].title
this.unencryptedCacheMap[title] = true
}
},
clearnUnencryptedCacheMap: function() {
this.unencryptedCacheMap = null
},
shouldEncryptTiddler: function(tiddler) {
if(tiddler.isTagged(this.tagForceEncrypt)) return true

if(store.isShadowTiddler([tiddler.title])
   || tiddler.title === "ListUnencrypted"
   || tiddler.isTagged("systemConfig")
   || tiddler.isTagged(this.tagDontEncrypt)
) return false

if(this.unencryptedCacheMap) {
return !this.unencryptedCacheMap[tiddler.title]
} else {
var unencryptedArray = this.getUnencryptedArray()
if(unencryptedArray.indexOf(tiddler) !== -1) return false

/*var unencryptedList = store.fetchTiddler("ListUnencrypted")
if(unencryptedList) {
var filter = unencryptedList.text
var tids = store.filterTiddlers(filter)
if(tids.indexOf(tiddler) !== -1) return false
}*/
}

return true
},

// TODO: review terms (locked, loaded, ..) – should be consistent
getVaultContent: function() {
var inTiddlerContent = this.getVaultTiddlerContent()
if(inTiddlerContent) return inTiddlerContent

// backward compatibility with 1.x

var el = document.getElementById(this.vaultAreaId)
return el ? el.innerHTML : null
},
// TODO: remove state; may be move some sections above to ~submodules instead
// loaded: falsy by default (we don't set it so that installing twice won't hurt)
isLocked: function() {
if(this.loaded) return false
var vaultContent = this.getVaultContent()
return vaultContent === null || this.isEncrypted(vaultContent)
},
existsAndIsLocked: function() {
return this.getVaultContent() !== null &&
this.isLocked()
},
// TODO: fix lingo (missing, prompt); review
// returns a boolean indicating success
load: function() {
if (!this.existsAndIsLocked()) {
// decorate

Tiddler.prototype.doNotSave = function() {
var shouldEncrypt = config.extensions.vault.shouldEncryptTiddler(this)
var mode = config.extensions.vault.doNotSaveMode
if((shouldEncrypt && mode == "encrypted" || !shouldEncrypt && mode == "unencrypted") &&
   config.extensions.vault.password) return true
return config.extensions.vault.originals.Tiddler_doNotSave.apply(this, arguments)
}

TiddlyWiki.prototype.allEncryptedTiddlersAsHtml = function() {
var prevMode = config.extensions.vault.doNotSaveMode
config.extensions.vault.doNotSaveMode = "unencrypted"

var result = this.allTiddlersAsHtml()


config.extensions.vault.doNotSaveMode = prevMode
return result
}

// TODO: review all decorations; may be move (encapsulate) some bits to ceVault methods
// decorate
window.updateOriginal = function(original, posDiv) {
var ceVault = config.extensions.vault
var vaultIsUpdatable = !ceVault.locateVaultArea(original) ||
!ceVault.existsAndIsLocked(true) || ceVault.shouldPurge

ceVault.populateUnencryptedCacheMap()

var password = ceVault.password
ceVault.updateVaultTiddler(!password ? null : ceVault.encrypt(store.allEncryptedTiddlersAsHtml(), password))


var revised = ceVault.originals.updateOriginal.apply(this, arguments)
//# move reporting results outside updateOriginal?
if(vaultIsUpdatable) {
displayMessage(config.messages[password ? 'vaultEncryptedInfo' : 'emptyVaultInfo'])
} else
displayMessage(config.messages.vaultUnchangedInfo)
if(config.extensions.vault.existsAndIsLocked()) return

createTiddlyButton(form, params.label, params.tooltip, this.onClick)
},
onClick: function(event) {
var form = event.target.parentElement
jQuery(form).empty()
var refreshForm = function() { config.macros.setPassword.refresh(form) }

var input = createTiddlyElement(form, 'input', null, null, null, {
type: 'password'
})
input.focus()

var ceVault = config.extensions.vault
jQuery(input).on('keydown', function(event) {
if(event.key === 'Escape') return refreshForm()

if(event.key !== 'Enter') return
ceVault.password = input.value

refreshForm()
displayMessage(ceVault.password ?
config.messages.passwordSet :
config.messages.passwordUnset)
return false
})
.on('blur', refreshForm)
// TODO: explain they have to type and press "enter" and/or add a button for that
// TODO: add also way to cancel (→ refresh form)


return false;
}
}

config.macros.purge = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
var label   = params[0] || "purge vault";
var tooltip = params[1] || "Delete locked vault";
var ceVault = config.extensions.vault
if (ceVault.existsAndIsLocked())
createTiddlyButton(place, label, tooltip, this.onClick);
},
onClick: function() {
var ceVault = config.extensions.vault
if (!ceVault.existsAndIsLocked())

alert(config.messages.noLockedVaultNoPurge);
else
if(confirm(config.messages.purgeConfirm)) {
ceVault.shouldPurge = true;
alert(config.messages.vaultPurgedInfo);
}
return false;
}
}

config.macros.ifLocked = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
if(config.extensions.vault.existsAndIsLocked())
wikify(params[0], place, null, tiddler);
}
}

config.macros.ifUnlocked = {
handler: function(place, macroName, params, wikifier, paramString, tiddler) {
if(!config.extensions.vault.existsAndIsLocked())

wikify(params[0], place, null, tiddler);
}
}
//}}}

пятница, 7 марта 2025 г. в 20:35:43 UTC+3, Yakov:
Reply all
Reply to author
Forward
0 new messages