const threshold = 0.97 const testSampleCount = 2; const minSampleReq = 4; var counter = 0; var recording = false; var sessionSamples = []; var sessionLabels = []; var buffer = []; function foo() { for(var i = 0; i < 10000000; i++); } function bar() { for(var i = 0; i < 100000000; i++); } function getPrefix() { var prefix = null; if (window.performance !== undefined) { if (window.performance.now !== undefined) prefix = ""; else { var browserPrefixes = ["webkit","moz","ms","o"]; // Test all vendor prefixes for(var i = 0; i < browserPrefixes.length; i++) { if (window.performance[browserPrefixes[i] + "Now"] != undefined) { prefix = browserPrefixes[i]; break; } } } } return prefix; } function getTime() { return (prefix === "") ? window.performance.now() : window.performance[prefix + "Now"](); } function doBenchmark() { if (prefix === null){ document.getElementById("displayRes").style.display = 'block'; document.getElementById("displayRes").textContent = "Your browser does not support High Resolution Time API"; } // else { // var startTime = getTime(); // foo(); // var test1 = getTime(); // bar(); // var test2 = getTime(); // document.getElementById("log").innerHTML += "

Browser hi-res time support test:

"; // document.getElementById("log").innerHTML += "Foo time: " + (test1 - startTime) + "
"; // document.getElementById("log").innerHTML += "Bar time: " + (test2 - test1) + "
"; // } } var prefix = getPrefix(); window.onload = doBenchmark; function showMatchTab(){ document.getElementById("createTab").style.display = "none"; document.getElementById("matchTab").style.display = "block"; document.getElementById('displayRes').style.display = "none"; } function showCreateTab(){ counter = 0; recording = false; sessionSamples = []; sessionLabels = []; buffer = []; document.getElementById("createTab").style.display = "block"; document.getElementById("matchTab").style.display = "none"; document.getElementById('displayRes').style.display = "none"; setWords(); } function startRecording(){ if(recording){ recording=false; //console.log('Stopped recording'); document.getElementById("recBttn").style = 'display: none'; document.getElementById("wrongBttn").style = 'display: none;'; document.getElementById("correctBttn").style = 'display: static;width: 20px; padding-bottom: 30px; margin-left: -30px;'; if (counter == minSampleReq-1){ document.getElementById("saveBttn").style = 'display: block; width: 20px; cursor: pointer;'; // document.getElementById('showAccuracy').style = 'display: static'; document.getElementById('matchFingerprint').style = 'display: static'; document.getElementById('textRecord').setAttribute("disabled", true); showAccuracy(); } extractFeatures(); showKDEGraph(); } else { buffer = []; document.getElementById("textRecord").value = ''; document.getElementById("recBttn").style = 'display: static;width: 20px; padding-bottom: 30px; margin-left: -30px;'; document.getElementById("wrongBttn").style = 'display: none;'; document.getElementById("correctBttn").style = 'display: none;'; // document.getElementById('showAccuracy').style = 'display: none'; document.getElementById('matchFingerprint').style = 'display: none'; recording=true; //console.log('Started recording'); } } function showAccuracy(){ document.getElementById('displayRes').textContent = ''; document.getElementById('displayRes').style = "display: block;"; var testSamples = sessionSamples.slice(Math.max(sessionSamples.length - testSampleCount, 0)); var testResult = []; for(var i=0; i < testSamples.length;i++){ var allSimilarities = []; for (var m=0; m < sessionSamples.length; m++){ var pred = cosineSimilarity(sessionSamples[m], testSamples[i]); allSimilarities.push(pred); } var med = calculateMedian(allSimilarities); var res = med > threshold ? 0 : 1; testResult.push(res); } var wins = 0; for(var n=0; n < testResult.length;n++){ if(testResult[n] == 0) { wins++; } } var proc = (wins/testResult.length) *100; console.log([proc,wins,testResult.length]) document.getElementById('displayRes').textContent = 'Consistancy: ' + proc + '%'; } async function stopRecordingTest(){ recording=false; var testSample = extractOneFeature(); var allSimilarities = []; for (var i=0; i < sessionSamples.length; i++){ var pred = cosineSimilarity(sessionSamples[i], testSample); allSimilarities.push(pred); } var med = calculateMedian(allSimilarities); var res = med > threshold ? "It really is you ❤️" : "👮 Intruder!!!"; var rest = med > threshold ? 0 : 1; if (rest == 0){ document.getElementById('correctBttn1').style = 'display: static;width: 20px; padding-bottom: 30px; margin-left: -30px;'; document.getElementById('wrongBttn1').style = "display: none"; } else if (rest == 1) { document.getElementById('correctBttn1').style = "display: none"; document.getElementById('wrongBttn1').style= 'display: static; width: 20px; padding-bottom: 30px; margin-left: -30px;'; } showKDEGraph(); document.getElementById('displayRes').style = "display: block;"; document.getElementById('displayRes').textContent = res; } function setWords(){ document.getElementById('randomWords').textContent = ''; for(var i=0; i< 4;i++){ var ttx = words(); document.getElementById('randomWords').textContent += ttx + ' '; } } function startRecordingTest(){ buffer = []; document.getElementById("testRecord").value = ''; recording=true; //console.log('Started recording'); } function calculateMedian(values) { values.sort( function(a,b) {return a - b;} ); var half = Math.floor(values.length/2); if(values.length % 2) return values[half]; else return (values[half-1] + values[half]) / 2.0; } function calculateAverage(array) { var total = 0; var count = 0; array.forEach(function(item, index) { total += item; count++; }); return total / count; } function extractFeatures(){ var uppArr = []; var downArr = []; var holdKeyTime = []; var ddKeyTime = []; var udKeyTime = []; for(var i = 0; i < buffer.length;i++){ if(buffer[i].event == "up"){ uppArr.push(buffer[i]); } if (buffer[i].event == "down"){ downArr.push(buffer[i]); } } if(uppArr.length != downArr.length || uppArr.length == 0 || downArr.length == 0) { document.getElementById("wrongBttn").style = 'display: static; width: 20px; padding-bottom: 30px; margin-left: -30px;'; document.getElementById("correctBttn").style = 'display: none'; return []; } for(var i=0; i < downArr.length;i++){ var timed = uppArr[i].time - downArr[i].time; holdKeyTime.push({key: 'H.'+ uppArr[i].key, time: timed }); if(i == downArr.length-1) break; var pkey = ('DD.'+ downArr[i].key + '.' + downArr[i+1].key); ddKeyTime.push({key: pkey, time: downArr[i+1].time - downArr[i].time}); var skey = 'UD.'+ uppArr[i].key + '.' + downArr[i+1].key; udKeyTime.push({ key: skey, time: downArr[i+1].time -uppArr[i].time}); } var featureReady = []; var labels = [] // var curUser = document.getElementById("nameField").value =='' ? 'NoName' : document.getElementById("nameField").value; for (var i=0; i < holdKeyTime.length; i++) { var totalTime = buffer[buffer.length-1].time - buffer[0].time; // var avrgSpeed = totalTime/holdKeyTime.length; if(i == holdKeyTime.length-1){ labels.push(holdKeyTime[i].key); featureReady.push(holdKeyTime[i].time/1000); break; } labels.push(holdKeyTime[i].key); labels.push(ddKeyTime[i].key); labels.push(udKeyTime[i].key); var hk_ = holdKeyTime[i].time/1000; var ddk_ = ddKeyTime[i].time/1000; var udk_ = udKeyTime[i].time/1000; var hk = hk_ < 0 ? (hk_ * -1) : hk_; var ddk = ddk_ < 0 ? (ddk_ * -1) : ddk_; var udk = udk_ < 0 ? (udk_ * -1) : udk_; featureReady.push(hk); featureReady.push(ddk); featureReady.push(udk); } // labels.push('avgSpeed') sessionSamples.push(featureReady); sessionLabels = labels; counter++; document.getElementById("counterView").textContent = counter; } function extractOneFeature(){ var uppArr = []; var downArr = []; var holdKeyTime = []; var ddKeyTime = []; var udKeyTime = []; for(var i = 0; i < buffer.length;i++){ if(buffer[i].event == "up"){ uppArr.push(buffer[i]); } if (buffer[i].event == "down"){ downArr.push(buffer[i]); } } if(uppArr.length != downArr.length || uppArr.length == 0 || downArr.length == 0) { document.getElementById("wrongBttn").style = 'display: static; width: 20px; padding-bottom: 30px; margin-left: -30px;'; document.getElementById("correctBttn").style = 'display: none'; return []; } for(var i=0; i < downArr.length;i++){ var timed = uppArr[i].time - downArr[i].time; holdKeyTime.push({key: 'H.'+ uppArr[i].key, time: timed }); if(i == downArr.length-1) break; var pkey = ('DD.'+ downArr[i].key + '.' + downArr[i+1].key); ddKeyTime.push({key: pkey, time: downArr[i+1].time - downArr[i].time}); var skey = 'UD.'+ uppArr[i].key + '.' + downArr[i+1].key; udKeyTime.push({ key: skey, time: downArr[i+1].time -uppArr[i].time}); } var featureReady = []; var labels = [] for (var i=0; i < holdKeyTime.length; i++) { if(i == holdKeyTime.length-1){ labels.push(holdKeyTime[i].key); featureReady.push(holdKeyTime[i].time/1000); break; } labels.push(holdKeyTime[i].key); labels.push(ddKeyTime[i].key); labels.push(udKeyTime[i].key); var hk_ = holdKeyTime[i].time/1000; var ddk_ = ddKeyTime[i].time/1000; var udk_ = udKeyTime[i].time/1000; var hk = hk_ < 0 ? (hk_ * -1) : hk_; var ddk = ddk_ < 0 ? (ddk_ * -1) : ddk_; var udk = udk_ < 0 ? (udk_ * -1) : udk_; featureReady.push(hk); featureReady.push(ddk); featureReady.push(udk); } return featureReady; } function saveSamples(){ //merege and convert samples to CSV var csv = sessionLabels +'\n'; sessionSamples.forEach(function(row) { csv += row.join(','); csv += "\n"; }); //console.log(csv); var hiddenElement = document.createElement('a'); hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv); hiddenElement.target = '_blank'; hiddenElement.download = 'signature.sms'; hiddenElement.click(); } //graph KDE function showKDEGraph(){ d3.select("svg").selectAll("*").remove(); var svg = d3.select("svg"), width = +svg.attr("width"), height = +svg.attr("height"), margin = {top: 20, right: 30, bottom: 30, left: 40}; var x = d3.scaleLinear() .domain([0, 1]) .range([margin.left, width - margin.right]); var y = d3.scaleLinear() .domain([0, 1]) .range([height - margin.bottom, margin.top]); svg.append("g") .attr("class", "axis axis--x") .attr("transform", "translate(0," + (height - margin.bottom) + ")") .call(d3.axisBottom(x)) .append("text") .attr("x", width - margin.right) .attr("y", -6) .attr("fill", "#000") .attr("text-anchor", "end") .attr("font-weight", "bold") .text("Time in seconds)"); svg.append("g") .attr("class", "axis axis--y") .attr("transform", "translate(" + margin.left + ",0)") .call(d3.axisLeft(y).ticks(null)); var faithful = sessionSamples[sessionSamples.length-1]; var n = faithful.length, bins = d3.histogram().domain(x.domain()).thresholds(sessionLabels.length)(faithful), density = kernelDensityEstimator(kernelEpanechnikov(0.0155), x.ticks(sessionLabels.length))(faithful); svg.insert("g", "*") .attr("fill", "#bbb") .selectAll("rect") .data(bins) .enter().append("rect") .attr("x", function(d) { return x(d.x0) + 1; }) .attr("y", function(d) { return y(d.length / n); }) .attr("width", function(d) { return x(d.x1) - x(d.x0) - 1; }) .attr("height", function(d) { return y(0) - y(d.length / n); }); svg.append("path") .datum(density) .attr("fill", "none") .attr("stroke", "#000") .attr("stroke-width", 1.5) .attr("stroke-linejoin", "round") .attr("d", d3.line() .curve(d3.curveBasis) .x(function(d) { return x(d[0]); }) .y(function(d) { return y(d[1]); })); } function kernelDensityEstimator(kernel, X) { return function(V) { return X.map(function(x) { return [x, d3.mean(V, function(v) { return kernel(x - v); })]; }); }; } function kernelEpanechnikov(k) { return function(v) { return Math.abs(v /= k) <= 1 ? 0.0199 * (1 - v * v) / k : 0; }; } function dotp(x, y) { function dotp_sum(a, b) { return a + b; } function dotp_times(a, i) { return x[i] * y[i]; } return x.map(dotp_times).reduce(dotp_sum, 0); } function cosineSimilarity(A,B){ var similarity = dotp(A, B) / (Math.sqrt(dotp(A,A)) * Math.sqrt(dotp(B,B))); return similarity; } window.addEventListener('load', () => { 'use strict'; document.getElementById("textRecord").value = ''; document.getElementById("testRecord").value = ''; // document.getElementById("nameField").value = ''; document.getElementById("counterView").value = counter; document.getElementById("minSampleView").textContent = minSampleReq; const charList = 'abcdefghijklmnopqrstuvwxyz@shift0123456789period!?'; document.addEventListener('keydown', event => { if (!recording) return; var key = event.key; if (key == '.') key = 'period'; // we are only interested in if (charList.indexOf(key) === -1) return; const el = { key: key.toLowerCase(), event:'down', time: performance.now() } buffer.push(el); }); document.addEventListener('keyup', event => { if (!recording) return; var key = event.key; if (key == '.') key = 'period'; // we are only interested in if (charList.indexOf(key) === -1) return; const el = { key: key.toLowerCase(), event:'up', time: performance.now() } buffer.push(el); }); });