dynamic certificate generation

This commit is contained in:
sairaj mote 2022-12-02 19:12:40 +05:30
parent 45b010aaf1
commit 2f74539d36
10 changed files with 10842 additions and 153 deletions

View File

@ -6,46 +6,59 @@
}
:root {
font-size: clamp(16px, 1.2vmax, 40px);
font-size: clamp(1rem, 1.2vmax, 1.2rem);
}
html,
body {
height: 100%;
}
body {
--accent-color: #2752ca;
--text-color: 17, 17, 17;
--text-color-light: 85, 85, 85;
--foreground-color: 255, 255, 255;
--background-color: rgba(var(--foreground-color), 1);
--dark-shade: #f4f4f4;
--error-color: #e53935;
--hue: 255;
--saturation: 61%;
--lightness: 39%;
--accent-color: #3d5afe;
--secondary-color: #ffac2e;
--text-color: 34, 34, 34;
--foreground-color: 252, 253, 255;
--background-color: 241, 243, 248;
--danger-color: rgb(255, 75, 75);
--green: #1cad59;
--yellow: rgb(220, 165, 0);
color: rgba(var(--text-color), 1);
background-size: cover;
background-color: rgba(var(--background-color), 1);
}
body[data-theme=dark] {
--accent-color: #4d74e0;
--text-color: 238, 238, 238;
--text-color-light: 170, 170, 170;
--foreground-color: 26, 26, 26;
--background-color: #111;
--dark-shade: #222;
--hue: 255;
--saturation: 39%;
--lightness: 70%;
background-color: var(--background-color);
--accent-color: #92a2ff;
--secondary-color: #d60739;
--text-color: 200, 200, 200;
--foreground-color: 27, 28, 29;
--background-color: 21, 22, 22;
--danger-color: rgb(255, 106, 106);
--green: #00e676;
--yellow: rgb(255, 213, 5);
}
body[data-theme=dark] ::-webkit-calendar-picker-indicator {
filter: invert(1);
}
a {
button {
padding: 1rem;
font-size: inherit;
background-color: var(--accent-color);
border: none;
border-radius: 0.3rem;
color: white;
font-weight: 500;
text-decoration: none;
color: var(--accent-color);
cursor: pointer;
}
#main {
display: grid;
grid-template-columns: 1fr 24rem;
input[type=date] {
padding: 1rem 0.8rem;
font-size: inherit;
background-color: rgba(var(--text-color), 0.06);
border: none;
border-radius: 0.3rem;
font-weight: 500;
}
#details {
@ -54,8 +67,38 @@ a {
display: grid;
gap: 1rem;
}
#details * {
font-family: "Roboto", sans-serif;
}
#cert_sec {
padding: 1.5rem;
font-family: "intern";
}
#issued_cert_list {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.5rem;
}
.cert-card {
display: grid;
gap: 0.5rem;
padding: 1rem;
background-color: rgba(var(--text-color), 0.06);
border-radius: 0.5rem;
}
.cert-card h4 {
font-size: 1.2rem;
}
.cert-card > * {
font-family: "Roboto", sans-serif;
}
@media (min-width: 768px) {
#main {
display: grid;
}
}

2
css/main.min.css vendored
View File

@ -1 +1 @@
*{padding:0;margin:0;box-sizing:border-box;font-family:"Times New Roman",Times,serif}:root{font-size:clamp(16px,1.2vmax,40px)}body{--accent-color: #2752ca;--text-color: 17, 17, 17;--text-color-light: 85, 85, 85;--foreground-color: 255, 255, 255;--background-color: rgba(var(--foreground-color), 1);--dark-shade: #f4f4f4;--error-color: #e53935;--hue: 255;--saturation: 61%;--lightness: 39%;color:rgba(var(--text-color), 1);background-size:cover}body[data-theme=dark]{--accent-color: #4d74e0;--text-color: 238, 238, 238;--text-color-light: 170, 170, 170;--foreground-color: 26, 26, 26;--background-color: #111;--dark-shade: #222;--hue: 255;--saturation: 39%;--lightness: 70%;background-color:var(--background-color)}a{font-weight:500;text-decoration:none;color:var(--accent-color)}#main{display:grid;grid-template-columns:1fr 24rem}#details{align-content:flex-start;padding:1.5rem;display:grid;gap:1rem}#cert_sec{padding:1.5rem;font-family:"intern"}
*{padding:0;margin:0;box-sizing:border-box;font-family:"Times New Roman",Times,serif}:root{font-size:clamp(1rem,1.2vmax,1.2rem)}html,body{height:100%}body{--accent-color: #3d5afe;--secondary-color: #ffac2e;--text-color: 34, 34, 34;--foreground-color: 252, 253, 255;--background-color: 241, 243, 248;--danger-color: rgb(255, 75, 75);--green: #1cad59;--yellow: rgb(220, 165, 0);color:rgba(var(--text-color), 1);background-color:rgba(var(--background-color), 1)}body[data-theme=dark]{--accent-color: #92a2ff;--secondary-color: #d60739;--text-color: 200, 200, 200;--foreground-color: 27, 28, 29;--background-color: 21, 22, 22;--danger-color: rgb(255, 106, 106);--green: #00e676;--yellow: rgb(255, 213, 5)}body[data-theme=dark] ::-webkit-calendar-picker-indicator{filter:invert(1)}button{padding:1rem;font-size:inherit;background-color:var(--accent-color);border:none;border-radius:.3rem;color:#fff;font-weight:500;cursor:pointer}input[type=date]{padding:1rem .8rem;font-size:inherit;background-color:rgba(var(--text-color), 0.06);border:none;border-radius:.3rem;font-weight:500}#details{align-content:flex-start;padding:1.5rem;display:grid;gap:1rem}#details *{font-family:"Roboto",sans-serif}#cert_sec{padding:1.5rem;font-family:"intern"}#issued_cert_list{display:flex;flex-direction:column;gap:1rem;padding:1.5rem}.cert-card{display:grid;gap:.5rem;padding:1rem;background-color:rgba(var(--text-color), 0.06);border-radius:.5rem}.cert-card h4{font-size:1.2rem}.cert-card>*{font-family:"Roboto",sans-serif}@media(min-width: 768px){#main{display:grid}}

View File

@ -4,52 +4,95 @@
box-sizing: border-box;
font-family: "Times New Roman", Times, serif;
}
:root {
font-size: clamp(16px, 1.2vmax, 40px);
}
body {
--accent-color: #2752ca;
--text-color: 17, 17, 17;
--text-color-light: 85, 85, 85;
--foreground-color: 255, 255, 255;
--background-color: rgba(var(--foreground-color), 1);
--dark-shade: #f4f4f4;
--error-color: #e53935;
--hue: 255;
--saturation: 61%;
--lightness: 39%;
color: rgba(var(--text-color), 1);
background-size: cover;
}
body[data-theme="dark"] {
--accent-color: #4d74e0;
--text-color: 238, 238, 238;
--text-color-light: 170, 170, 170;
--foreground-color: 26, 26, 26;
--background-color: #111;
--dark-shade: #222;
--hue: 255;
--saturation: 39%;
--lightness: 70%;
background-color: var(--background-color);
font-size: clamp(1rem, 1.2vmax, 1.2rem);
}
a {
font-weight: 500;
text-decoration: none;
color: var(--accent-color);
html,
body {
height: 100%;
}
#main {
display: grid;
grid-template-columns: 1fr 24rem;
body {
--accent-color: #3d5afe;
--secondary-color: #ffac2e;
--text-color: 34, 34, 34;
--foreground-color: 252, 253, 255;
--background-color: 241, 243, 248;
--danger-color: rgb(255, 75, 75);
--green: #1cad59;
--yellow: rgb(220, 165, 0);
color: rgba(var(--text-color), 1);
background-color: rgba(var(--background-color), 1);
}
body[data-theme="dark"] {
--accent-color: #92a2ff;
--secondary-color: #d60739;
--text-color: 200, 200, 200;
--foreground-color: 27, 28, 29;
--background-color: 21, 22, 22;
--danger-color: rgb(255, 106, 106);
--green: #00e676;
--yellow: rgb(255, 213, 5);
::-webkit-calendar-picker-indicator {
filter: invert(1);
}
}
button {
padding: 1rem;
font-size: inherit;
background-color: var(--accent-color);
border: none;
border-radius: 0.3rem;
color: white;
font-weight: 500;
cursor: pointer;
}
input[type="date"] {
padding: 1rem 0.8rem;
font-size: inherit;
background-color: rgba(var(--text-color), 0.06);
border: none;
border-radius: 0.3rem;
font-weight: 500;
}
#details {
align-content: flex-start;
padding: 1.5rem;
display: grid;
gap: 1rem;
* {
font-family: "Roboto", sans-serif;
}
}
#cert_sec {
padding: 1.5rem;
font-family: "intern";
}
#issued_cert_list {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.5rem;
}
.cert-card {
display: grid;
gap: 0.5rem;
padding: 1rem;
background-color: rgba(var(--text-color), 0.06);
border-radius: 0.5rem;
h4 {
font-size: 1.2rem;
}
& > * {
font-family: "Roboto", sans-serif;
}
}
@media (min-width: 768px) {
#main {
display: grid;
}
}

View File

@ -10,17 +10,35 @@
<link
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700;800;900&family=Roboto:wght@400;500;700&display=swap"
rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="qrcode-svg.min.js"></script>
<script src="components.js"></script>
<script src="svg2pdf.js"></script>
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
<script src="scripts/components.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js" defer></script>
<script src="scripts/qrcode-svg.min.js" defer></script>
<script src="scripts/svg2pdf.js" defer></script>
<script id="floGlobals">
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
const floGlobals = {
blockchain: "FLO",
adminID: "FKAEdnPfjXLHSYwrXQu377ugN4tXU7VGdf",
application: "TEST_MODE",
RMincorporationID: "FKNW5eCCp2SnJMJ6pLLpUCvk5hAage8Jtk",
RIBC_id: "FDaX363r1ooANA9A2erhehhigNTnidq3o4",
RM_CertificateIssuer_id: "FFCpiaZi31TpbYw5q5VNk8qJMeDiTLgsrE",
}
</script>
<script src="scripts/lib.js"></script>
<script src="scripts/floCrypto.js"></script>
<script src="scripts/floBlockchainAPI.js"></script>
</head>
<body>
<section>
<ul id="issued_cert_list"></ul>
</section>
<main id="main">
<section>
<svg id="mySVG" width="1400" height="980" viewBox="0 0 1400 980" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg id="certificate_svg" width="1400" height="980" viewBox="0 0 1400 980" fill="none"
xmlns="http://www.w3.org/2000/svg">
<g id="certificate">
<rect width="1400" height="980" fill="white" />
<g id="Frame" clip-path="url(#clip0_60_2)">
@ -60,29 +78,28 @@
<tspan x="596.464" y="844.573">Scan to verify this certificate</tspan>
</text>
<g id="cert_qr"></g>
<text id="certificate_type" fill="black" xml:space="preserve" style="white-space: pre"
font-family="Times New Roman" font-size="36" font-weight="bold" letter-spacing="0.07em">
<text id="certificate_type" fill="black" xml:space="preserve" style="white-space: pre;"
font-family="Times New Roman" font-size="36" font-weight="bold">
<tspan x="506.293" y="217.646">CERTIFICATE TYPE</tspan>
</text>
<rect id="Rectangle 69" x="62" y="57" width="1277" height="865" rx="12" stroke="url(#paint2_linear_60_2)"
stroke-width="8" />
<rect id="Rectangle 69" x="62" y="57" width="1277" height="865" rx="12" stroke="#5B21FF" stroke-width="8" />
<text id="cert_data" fill="#333333" xml:space="preserve" style="white-space: pre"
font-family="Times New Roman" font-size="18">
</text>
<g id="Group 12">
<text id="FLO INCORPORATION ID" fill="#353535" xml:space="preserve" style="white-space: pre"
font-family="Times New Roman" font-size="12">
<tspan x="1099.93" y="790.049">FLO INCORPORATION ID</tspan>
<tspan x="1024" y="790.049">INTERNSHIP CONTRACT ADDRESS</tspan>
</text>
<text id="digital_id" fill="#373737" xml:space="preserve" style="white-space: pre"
font-family="Times New Roman" font-size="16">
<tspan x="936.531" y="816.732">FKNW5eCCp2SnJMJ6pLLpUCvk5hAage8Jtk</tspan>
<tspan x="936.531" y="816.732">FDaX363r1ooANA9A2erhehhigNTnidq3o4</tspan>
</text>
</g>
<g id="Group 16">
<text id="CERTIFICATE ISSUER" fill="#353535" xml:space="preserve" style="white-space: pre"
font-family="Times New Roman" font-size="12">
<tspan x="162" y="793.049">CERTIFICATE ISSUER</tspan>
<tspan x="162" y="793.049">CERTIFICATE ISSUER ADDRESS</tspan>
</text>
<text id="digital_id_2" fill="#373737" xml:space="preserve" style="white-space: pre"
font-family="Times New Roman" font-size="16">
@ -120,95 +137,303 @@
</section>
<section id="details">
<h3>Fill the details</h3>
<sm-input type="text" id="name_field" data-bind="name" placeholder="Name" animate></sm-input>
<!-- <h3>Fill the details</h3>
<sm-input id="cert_type_field" data-bind="name" placeholder="Certificate type" animate></sm-input>
<sm-input id="name_field" data-bind="name" placeholder="Name" animate></sm-input>
<input type="date" id="issue_date_field" data-bind="issue_date" placeholder="Issue date">
<sm-input type="text" id="intern_id_field" data-bind="intern_flo_id" placeholder="Intern FLO ID" animate>
<sm-input id="intern_id_field" data-bind="intern_flo_id" placeholder="Intern FLO ID" animate>
</sm-input>
<sm-input type="text" id="flo_link" data-bind="intern_flo_id" placeholder="FLO trasansaction link" animate>
<sm-input id="flo_link" data-bind="intern_flo_id" placeholder="FLO trasansaction link" animate>
</sm-input>
<sm-textarea id="cert_text" placeholder="data" rows="4"></sm-textarea>
<sm-input type="text" id="digital_id_field" data-bind="digital_id" placeholder="Incorporation id"
value="FKNW5eCCp2SnJMJ6pLLpUCvk5hAage8Jtk" animate></sm-input>
<sm-textarea id="cert_text" placeholder="data" rows="4"></sm-textarea> -->
<button id="download_btn">Download</button>
</section>
</main>
<script>
const { jsPDF } = window.jspdf;
const { svg2pdf } = window.svg2pdf;
function convertSvgToPdf(svgElement) {
const margin = 5
const sizeArray = new Array(2)
sizeArray[0] = 1400 + 2 * margin
sizeArray[1] = 980 + 2 * margin
// eslint-disable-next-line no-undef,new-cap
const jsPdf = new jsPDF({
orientation: sizeArray[0] > sizeArray[1] ? 'l' : 'p',
unit: 'pt',
format: sizeArray,
compress: true,
floatPrecision: 'smart'
})
jsPdf
.svg(svgElement, {
x: 0,
y: 0,
width: 1400,
height: 980
})
.then(() => {
// save the created pdf
jsPdf.save('myPDF.pdf')
})
}
var svgElement = document.getElementById('mySVG');
//svgElement.style.fill = '#fff'
document.getElementById('name_field').addEventListener('input', e => {
document.getElementById('main_name').firstElementChild.textContent = e.target.value
alignTextCenter(document.getElementById('main_name').firstElementChild)
})
document.getElementById('issue_date_field').addEventListener('input', e => {
document.getElementById('issue_date').firstElementChild.textContent = e.target.value
alignTextCenter(document.getElementById('issue_date').firstElementChild)
})
document.getElementById('intern_id_field').addEventListener('input', e => {
document.getElementById('intern_flo_id').firstElementChild.textContent = e.target.value
alignTextCenter(document.getElementById('intern_flo_id').firstElementChild)
})
document.getElementById('cert_text').addEventListener('input', e => {
document.getElementById('cert_data').innerHTML = ''
e.target.value.split('\n').forEach((line, index) => {
let tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan')
tspan.textContent = line
tspan.setAttribute('x', '0')
if (index === 0) {
tspan.setAttribute('y', '500')
// Use instead of document.getElementById
const domRefs = {}
function getRef(elementId) {
if (!domRefs.hasOwnProperty(elementId)) {
domRefs[elementId] = {
count: 1,
ref: null,
};
return document.getElementById(elementId);
} else {
if (domRefs[elementId].count < 3) {
domRefs[elementId].count = domRefs[elementId].count + 1;
return document.getElementById(elementId);
} else {
if (!domRefs[elementId].ref)
domRefs[elementId].ref = document.getElementById(elementId);
return domRefs[elementId].ref;
}
tspan.setAttribute('dy', '1.5em')
document.getElementById('cert_data').appendChild(tspan)
alignTextCenter(tspan)
}
}
const appState = {
params: {},
}
const generalPages = ['sign_up', 'sign_in', 'loading', 'landing']
function routeTo(targetPage, options = {}) {
const { firstLoad } = options
const routingAnimation = { in: slideInUp, out: slideOutUp }
let pageId
let subPageId1
let searchParams
let params
if (targetPage === '') {
try {
if (floDapps.user.id)
pageId = 'dashboard_page'
} catch (e) {
pageId = 'landing'
}
} else {
if (targetPage.includes('/')) {
if (targetPage.includes('?')) {
const splitAddress = targetPage.split('?')
searchParams = splitAddress.pop();
[, pageId, subPageId1] = splitAddress.pop().split('/')
} else {
[, pageId, subPageId1] = targetPage.split('/')
}
} else {
pageId = targetPage
}
}
if (!document.querySelector(`#${pageId}`)?.classList.contains('inner-page')) return
try {
if (floDapps.user.id && (generalPages.includes(pageId))) {
history.replaceState(null, null, '#/dashboard_page');
pageId = 'dashboard_page'
}
} catch (e) {
if (!(generalPages.includes(pageId))) return
}
appState.currentPage = pageId
if (searchParams) {
const urlSearchParams = new URLSearchParams('?' + searchParams);
params = Object.fromEntries(urlSearchParams.entries());
}
if (params)
appState.params = params
switch (pageId) {
case 'landing':
break;
}
if (appState.lastPage !== pageId) {
if (document.querySelector('.nav-list__item--active'))
document.querySelector('.nav-list__item--active').classList.remove('nav-list__item--active');
const targetListItem = [...document.querySelectorAll(`a.nav-list__item`)].find(item => item.href.includes(pageId))
if (targetListItem) {
targetListItem.classList.add('nav-list__item--active')
getRef('main_nav').classList.remove('hide-on-mobile')
} else {
getRef('main_nav').classList.add('hide-on-mobile')
}
document.querySelectorAll('.page').forEach(page => page.classList.add('hidden'))
getRef(pageId).closest('.page').classList.remove('hidden')
let ogOverflow = getRef(pageId).parentNode.style.overflow
getRef(pageId).parentNode.style.overflow = 'hidden';
if (appState.lastPage) {
getRef(appState.lastPage).animate(routingAnimation.out, { duration: floGlobals.prefersReducedMotion ? 0 : 150, fill: 'forwards', easing: 'ease' }).onfinish = (e) => {
e.target.effect.target.classList.add('hidden')
}
}
getRef(pageId).classList.remove('hidden')
getRef(pageId).animate(routingAnimation.in, { duration: floGlobals.prefersReducedMotion ? 0 : 150, fill: 'forwards', easing: 'ease' }).onfinish = (e) => {
getRef(pageId).parentNode.style.overflow = ogOverflow;
switch (pageId) {
case 'sign_in':
break;
}
appState.lastPage = pageId
}
}
}
</script>
<script>
const { html, render: renderElem } = uhtml;
function getFormattedTime(timestamp, format) {
try {
timestamp = parseInt(timestamp)
if (String(timestamp).length < 13)
timestamp *= 1000
let [day, month, date, year] = new Date(timestamp).toString().split(' '),
minutes = new Date(timestamp).getMinutes(),
hours = new Date(timestamp).getHours(),
currentTime = new Date().toString().split(' ')
minutes = minutes < 10 ? `0${minutes}` : minutes
let finalHours = ``;
if (hours > 12)
finalHours = `${hours - 12}:${minutes}`
else if (hours === 0)
finalHours = `12:${minutes}`
else
finalHours = `${hours}:${minutes}`
finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM`
switch (format) {
case 'date-only':
return `${month} ${date}, ${year}`;
break;
case 'time-only':
return finalHours;
default:
return `${month} ${date}, ${year} at ${finalHours}`;
}
} catch (e) {
console.error(e);
return timestamp;
}
}
window.onload = () => {
const { jsPDF } = window.jspdf;
const { svg2pdf } = window.svg2pdf;
function convertSvgToPdf(element) {
const margin = 5
const sizeArray = new Array(2)
sizeArray[0] = 1400 + 2 * margin
sizeArray[1] = 980 + 2 * margin
// eslint-disable-next-line no-undef,new-cap
const jsPdf = new jsPDF({
orientation: sizeArray[0] > sizeArray[1] ? 'l' : 'p',
unit: 'pt',
format: sizeArray,
compress: true,
floatPrecision: 'smart'
})
jsPdf
.svg(element, {
x: 0,
y: 0,
width: 1400,
height: 980
})
.then(() => {
// save the created pdf
jsPdf.save('myPDF.pdf')
})
}
getRef('download_btn').addEventListener('click', e => {
var myPDF = convertSvgToPdf(getRef('certificate_svg'))
})
})
document.getElementById('flo_link').addEventListener('input', e => {
document.getElementById('cert_qr').innerHTML = ''
let svgNode = QRCode({
msg: e.target.value.trim(),
dim: 156
});
console.log(e.target.value.trim())
svgNode.setAttribute('x', 626);
svgNode.setAttribute('y', 670);
document.getElementById('cert_qr').appendChild(svgNode);
})
let a = document.getElementById('download_btn')
a.addEventListener('click', e => {
var myPDF = convertSvgToPdf(svgElement)
})
getRef('issued_cert_list').addEventListener('click', e => {
if (e.target.closest('button')) {
const txId = e.target.closest('button').getAttribute('data-txid')
render.certificate(txId)
}
})
readTx()
}
function alignTextCenter(element) {
element.setAttribute('x', `${700 - element.getBoundingClientRect().width / 2}`)
}
// return string between two strings
function getBetween(str, start, end) {
return str.substring(str.indexOf(start) + start.length, str.indexOf(end));
}
// get first three words of a string
function getFirstThreeWords(str) {
return str.split(' ').slice(0, 3).join(' ')
}
// split string into lines of max characters per line without breaking words
function splitIntoLines(str, maxChars = 132) {
let lines = []
let words = str.split(' ')
let line = ''
words.forEach(word => {
if (line.length + word.length < maxChars) {
line += word + ' '
} else {
lines.push(line)
line = word + ' '
}
})
lines.push(line)
return lines
}
const render = {
issuedCertCard(details) {
const { floData, txid } = details
const name = getBetween(floData, 'certifies that', 'FLO ID')
const floId = floData.match(/\b\w{30,36}\b/)
return html`
<li class="cert-card">
<h4>${name}</h4>
<p>${floId}</p>
<button data-txid=${txid}>View</button>
</li>
`;
},
certificate(txid) {
const { floData, time } = floGlobals.validCerts.get(txid)
const name = getBetween(floData, 'certifies that', 'FLO ID')
const floId = floData.match(/\b\w{30,36}\b/)
getRef('certificate_type').firstElementChild.textContent = getFirstThreeWords(floData).toUpperCase();
alignTextCenter(getRef('certificate_type').firstElementChild)
getRef('main_name').firstElementChild.textContent = name.toUpperCase();
alignTextCenter(getRef('main_name').firstElementChild)
getRef('issue_date').firstElementChild.textContent = `Issued: ${getFormattedTime(time, 'date-only')}`
alignTextCenter(getRef('issue_date').firstElementChild)
getRef('intern_flo_id').firstElementChild.textContent = floId
alignTextCenter(getRef('intern_flo_id').firstElementChild)
getRef('cert_data').innerHTML = ''
splitIntoLines(floData).forEach((line, index) => {
let tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan')
tspan.textContent = line
tspan.setAttribute('x', '0')
if (index === 0) {
tspan.setAttribute('y', '480')
}
tspan.setAttribute('dy', '1.5em')
getRef('cert_data').appendChild(tspan)
alignTextCenter(tspan)
})
let certificateVerification = ''
switch (getFirstThreeWords(floData)) {
case "CERTIFICATE OF INTERNSHIP":
certificateVerification = 'internCertificate';
break;
case "CERTIFICATE OF EMPLOYMENT":
certificateVerification = 'employeeCertificate';
break;
case "CERTIFICATE OF VOLUNTEERSHIP":
certificateVerification = 'volunteerCertificate';
break;
case "CERTIFICATE OF PARTICIPATION":
certificateVerification = 'participationCertificate';
break;
}
getRef('cert_qr').innerHTML = ''
let svgNode = QRCode({
msg: `https://www.ranchimall.net/verify/?${certificateVerification}=${txid}`,
dim: 156
});
svgNode.setAttribute('x', 626);
svgNode.setAttribute('y', 670);
getRef('cert_qr').appendChild(svgNode);
}
}
floGlobals.validCerts = new Map()
function readTx() {
floBlockchainAPI.readAllTxs('FFCpiaZi31TpbYw5q5VNk8qJMeDiTLgsrE').then(res => {
res.forEach(tx => {
const { vin, vout, time, floData } = tx
const iVerify = vin.some(i => i.addr === floGlobals.RM_CertificateIssuer_id);
const oVerify = vout.some(o => [floGlobals.RMincorporationID, floGlobals.RIBC_id].includes(o.scriptPubKey.addresses[0]));
const cVerify = tx.floData.startsWith('CERTIFICATE OF')
const hasFloId = /\b\w{30,36}\b/.test(floData)
if (iVerify && oVerify && cVerify && hasFloId) {
floGlobals.validCerts.set(tx.txid, tx)
}
})
renderElem(getRef('issued_cert_list'), html`${[...floGlobals.validCerts.values()].map(cert => render.issuedCertCard(cert))}`)
})
}
</script>
</body>

580
scripts/floBlockchainAPI.js Normal file
View File

@ -0,0 +1,580 @@
(function (EXPORTS) { //floBlockchainAPI v2.3.3d
/* FLO Blockchain Operator to send/receive data from blockchain using API calls*/
'use strict';
const floBlockchainAPI = EXPORTS;
const DEFAULT = {
blockchain: floGlobals.blockchain,
apiURL: {
FLO: ['https://flosight.duckdns.org/'],
FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/']
},
sendAmt: 0.001,
fee: 0.0005,
minChangeAmt: 0.0005,
receiverID: floGlobals.adminID
};
Object.defineProperties(floBlockchainAPI, {
sendAmt: {
get: () => DEFAULT.sendAmt,
set: amt => !isNaN(amt) ? DEFAULT.sendAmt = amt : null
},
fee: {
get: () => DEFAULT.fee,
set: fee => !isNaN(fee) ? DEFAULT.fee = fee : null
},
defaultReceiver: {
get: () => DEFAULT.receiverID,
set: floID => DEFAULT.receiverID = floID
},
blockchain: {
get: () => DEFAULT.blockchain
}
});
if (floGlobals.sendAmt) floBlockchainAPI.sendAmt = floGlobals.sendAmt;
if (floGlobals.fee) floBlockchainAPI.fee = floGlobals.fee;
Object.defineProperties(floGlobals, {
sendAmt: {
get: () => DEFAULT.sendAmt,
set: amt => !isNaN(amt) ? DEFAULT.sendAmt = amt : null
},
fee: {
get: () => DEFAULT.fee,
set: fee => !isNaN(fee) ? DEFAULT.fee = fee : null
}
});
const allServerList = new Set(floGlobals.apiURL && floGlobals.apiURL[DEFAULT.blockchain] ? floGlobals.apiURL[DEFAULT.blockchain] : DEFAULT.apiURL[DEFAULT.blockchain]);
var serverList = Array.from(allServerList);
var curPos = floCrypto.randInt(0, serverList.length - 1);
function fetch_retry(apicall, rm_flosight) {
return new Promise((resolve, reject) => {
let i = serverList.indexOf(rm_flosight)
if (i != -1) serverList.splice(i, 1);
curPos = floCrypto.randInt(0, serverList.length - 1);
fetch_api(apicall, false)
.then(result => resolve(result))
.catch(error => reject(error));
})
}
function fetch_api(apicall, ic = true) {
return new Promise((resolve, reject) => {
if (serverList.length === 0) {
if (ic) {
serverList = Array.from(allServerList);
curPos = floCrypto.randInt(0, serverList.length - 1);
fetch_api(apicall, false)
.then(result => resolve(result))
.catch(error => reject(error));
} else
reject("No floSight server working");
} else {
let flosight = serverList[curPos];
fetch(flosight + apicall).then(response => {
if (response.ok)
response.json().then(data => resolve(data));
else {
fetch_retry(apicall, flosight)
.then(result => resolve(result))
.catch(error => reject(error));
}
}).catch(error => {
fetch_retry(apicall, flosight)
.then(result => resolve(result))
.catch(error => reject(error));
})
}
})
}
Object.defineProperties(floBlockchainAPI, {
serverList: {
get: () => Array.from(serverList)
},
current_server: {
get: () => serverList[curPos]
}
});
//Promised function to get data from API
const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function (apicall) {
return new Promise((resolve, reject) => {
//console.log(apicall);
fetch_api(apicall)
.then(result => resolve(result))
.catch(error => reject(error));
});
}
//Get balance for the given Address
const getBalance = floBlockchainAPI.getBalance = function (addr) {
return new Promise((resolve, reject) => {
promisedAPI(`api/addr/${addr}/balance`)
.then(balance => resolve(parseFloat(balance)))
.catch(error => reject(error));
});
}
//Send Tx to blockchain
const sendTx = floBlockchainAPI.sendTx = function (senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) {
return new Promise((resolve, reject) => {
if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
else if (!floCrypto.validateFloID(senderAddr))
return reject(`Invalid address : ${senderAddr}`);
else if (!floCrypto.validateFloID(receiverAddr))
return reject(`Invalid address : ${receiverAddr}`);
else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr))
return reject("Invalid Private key!");
else if (typeof sendAmt !== 'number' || sendAmt <= 0)
return reject(`Invalid sendAmt : ${sendAmt}`);
getBalance(senderAddr).then(balance => {
var fee = DEFAULT.fee;
if (balance < sendAmt + fee)
return reject("Insufficient FLO balance!");
//get unconfirmed tx list
promisedAPI(`api/addr/${senderAddr}`).then(result => {
readTxs(senderAddr, 0, result.unconfirmedTxApperances).then(result => {
let unconfirmedSpent = {};
for (let tx of result.items)
if (tx.confirmations == 0)
for (let vin of tx.vin)
if (vin.addr === senderAddr) {
if (Array.isArray(unconfirmedSpent[vin.txid]))
unconfirmedSpent[vin.txid].push(vin.vout);
else
unconfirmedSpent[vin.txid] = [vin.vout];
}
//get utxos list
promisedAPI(`api/addr/${senderAddr}/utxo`).then(utxos => {
//form/construct the transaction data
var trx = bitjs.transaction();
var utxoAmt = 0.0;
for (var i = utxos.length - 1;
(i >= 0) && (utxoAmt < sendAmt + fee); i--) {
//use only utxos with confirmations (strict_utxo mode)
if (utxos[i].confirmations || !strict_utxo) {
if (utxos[i].txid in unconfirmedSpent && unconfirmedSpent[utxos[i].txid].includes(utxos[i].vout))
continue; //A transaction has already used the utxo, but is unconfirmed.
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
utxoAmt += utxos[i].amount;
};
}
if (utxoAmt < sendAmt + fee)
reject("Insufficient FLO: Some UTXOs are unconfirmed");
else {
trx.addoutput(receiverAddr, sendAmt);
var change = utxoAmt - sendAmt - fee;
if (change > DEFAULT.minChangeAmt)
trx.addoutput(senderAddr, change);
trx.addflodata(floData.replace(/\n/g, ' '));
var signedTxHash = trx.sign(privKey, 1);
broadcastTx(signedTxHash)
.then(txid => resolve(txid))
.catch(error => reject(error))
}
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
});
}
//Write Data into blockchain
floBlockchainAPI.writeData = function (senderAddr, data, privKey, receiverAddr = DEFAULT.receiverID, options = {}) {
let strict_utxo = options.strict_utxo === false ? false : true,
sendAmt = isNaN(options.sendAmt) ? DEFAULT.sendAmt : options.sendAmt;
return new Promise((resolve, reject) => {
if (typeof data != "string")
data = JSON.stringify(data);
sendTx(senderAddr, receiverAddr, sendAmt, privKey, data, strict_utxo)
.then(txid => resolve(txid))
.catch(error => reject(error));
});
}
//merge all UTXOs of a given floID into a single UTXO
floBlockchainAPI.mergeUTXOs = function (floID, privKey, floData = '') {
return new Promise((resolve, reject) => {
if (!floCrypto.validateFloID(floID))
return reject(`Invalid floID`);
if (!floCrypto.verifyPrivKey(privKey, floID))
return reject("Invalid Private Key");
if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
var trx = bitjs.transaction();
var utxoAmt = 0.0;
var fee = DEFAULT.fee;
promisedAPI(`api/addr/${floID}/utxo`).then(utxos => {
for (var i = utxos.length - 1; i >= 0; i--)
if (utxos[i].confirmations) {
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
utxoAmt += utxos[i].amount;
}
trx.addoutput(floID, utxoAmt - fee);
trx.addflodata(floData.replace(/\n/g, ' '));
var signedTxHash = trx.sign(privKey, 1);
broadcastTx(signedTxHash)
.then(txid => resolve(txid))
.catch(error => reject(error))
}).catch(error => reject(error))
})
}
/**Write data into blockchain from (and/or) to multiple floID
* @param {Array} senderPrivKeys List of sender private-keys
* @param {string} data FLO data of the txn
* @param {Array} receivers List of receivers
* @param {boolean} preserveRatio (optional) preserve ratio or equal contribution
* @return {Promise}
*/
floBlockchainAPI.writeDataMultiple = function (senderPrivKeys, data, receivers = [DEFAULT.receiverID], preserveRatio = true) {
return new Promise((resolve, reject) => {
if (!Array.isArray(senderPrivKeys))
return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array");
if (!preserveRatio) {
let tmp = {};
let amount = (DEFAULT.sendAmt * receivers.length) / senderPrivKeys.length;
senderPrivKeys.forEach(key => tmp[key] = amount);
senderPrivKeys = tmp;
}
if (!Array.isArray(receivers))
return reject("Invalid receivers: Receivers must be Array");
else {
let tmp = {};
let amount = DEFAULT.sendAmt;
receivers.forEach(floID => tmp[floID] = amount);
receivers = tmp
}
if (typeof data != "string")
data = JSON.stringify(data);
sendTxMultiple(senderPrivKeys, receivers, data)
.then(txid => resolve(txid))
.catch(error => reject(error))
})
}
/**Send Tx from (and/or) to multiple floID
* @param {Array or Object} senderPrivKeys List of sender private-key (optional: with coins to be sent)
* @param {Object} receivers List of receivers with respective amount to be sent
* @param {string} floData FLO data of the txn
* @return {Promise}
*/
const sendTxMultiple = floBlockchainAPI.sendTxMultiple = function (senderPrivKeys, receivers, floData = '') {
return new Promise((resolve, reject) => {
if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
let senders = {},
preserveRatio;
//check for argument validations
try {
let invalids = {
InvalidSenderPrivKeys: [],
InvalidSenderAmountFor: [],
InvalidReceiverIDs: [],
InvalidReceiveAmountFor: []
}
let inputVal = 0,
outputVal = 0;
//Validate sender privatekeys (and send amount if passed)
//conversion when only privateKeys are passed (preserveRatio mode)
if (Array.isArray(senderPrivKeys)) {
senderPrivKeys.forEach(key => {
try {
if (!key)
invalids.InvalidSenderPrivKeys.push(key);
else {
let floID = floCrypto.getFloID(key);
senders[floID] = {
wif: key
}
}
} catch (error) {
invalids.InvalidSenderPrivKeys.push(key)
}
})
preserveRatio = true;
}
//conversion when privatekeys are passed with send amount
else {
for (let key in senderPrivKeys) {
try {
if (!key)
invalids.InvalidSenderPrivKeys.push(key);
else {
if (typeof senderPrivKeys[key] !== 'number' || senderPrivKeys[key] <= 0)
invalids.InvalidSenderAmountFor.push(key);
else
inputVal += senderPrivKeys[key];
let floID = floCrypto.getFloID(key);
senders[floID] = {
wif: key,
coins: senderPrivKeys[key]
}
}
} catch (error) {
invalids.InvalidSenderPrivKeys.push(key)
}
}
preserveRatio = false;
}
//Validate the receiver IDs and receive amount
for (let floID in receivers) {
if (!floCrypto.validateFloID(floID))
invalids.InvalidReceiverIDs.push(floID);
if (typeof receivers[floID] !== 'number' || receivers[floID] <= 0)
invalids.InvalidReceiveAmountFor.push(floID);
else
outputVal += receivers[floID];
}
//Reject if any invalids are found
for (let i in invalids)
if (!invalids[i].length)
delete invalids[i];
if (Object.keys(invalids).length)
return reject(invalids);
//Reject if given inputVal and outputVal are not equal
if (!preserveRatio && inputVal != outputVal)
return reject(`Input Amount (${inputVal}) not equal to Output Amount (${outputVal})`);
} catch (error) {
return reject(error)
}
//Get balance of senders
let promises = [];
for (let floID in senders)
promises.push(getBalance(floID));
Promise.all(promises).then(results => {
let totalBalance = 0,
totalFee = DEFAULT.fee,
balance = {};
//Divide fee among sender if not for preserveRatio
if (!preserveRatio)
var dividedFee = totalFee / Object.keys(senders).length;
//Check if balance of each sender is sufficient enough
let insufficient = [];
for (let floID in senders) {
balance[floID] = parseFloat(results.shift());
if (isNaN(balance[floID]) || (preserveRatio && balance[floID] <= totalFee) ||
(!preserveRatio && balance[floID] < senders[floID].coins + dividedFee))
insufficient.push(floID);
totalBalance += balance[floID];
}
if (insufficient.length)
return reject({
InsufficientBalance: insufficient
})
//Calculate totalSentAmount and check if totalBalance is sufficient
let totalSendAmt = totalFee;
for (let floID in receivers)
totalSendAmt += receivers[floID];
if (totalBalance < totalSendAmt)
return reject("Insufficient total Balance");
//Get the UTXOs of the senders
let promises = [];
for (let floID in senders)
promises.push(promisedAPI(`api/addr/${floID}/utxo`));
Promise.all(promises).then(results => {
let wifSeq = [];
var trx = bitjs.transaction();
for (let floID in senders) {
let utxos = results.shift();
let sendAmt;
if (preserveRatio) {
let ratio = (balance[floID] / totalBalance);
sendAmt = totalSendAmt * ratio;
} else
sendAmt = senders[floID].coins + dividedFee;
let wif = senders[floID].wif;
let utxoAmt = 0.0;
for (let i = utxos.length - 1;
(i >= 0) && (utxoAmt < sendAmt); i--) {
if (utxos[i].confirmations) {
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
wifSeq.push(wif);
utxoAmt += utxos[i].amount;
}
}
if (utxoAmt < sendAmt)
return reject("Insufficient balance:" + floID);
let change = (utxoAmt - sendAmt);
if (change > 0)
trx.addoutput(floID, change);
}
for (let floID in receivers)
trx.addoutput(floID, receivers[floID]);
trx.addflodata(floData.replace(/\n/g, ' '));
for (let i = 0; i < wifSeq.length; i++)
trx.signinput(i, wifSeq[i], 1);
var signedTxHash = trx.serialize();
broadcastTx(signedTxHash)
.then(txid => resolve(txid))
.catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
})
}
//Broadcast signed Tx in blockchain using API
const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) {
return new Promise((resolve, reject) => {
if (signedTxHash.length < 1)
return reject("Empty Signature");
var url = serverList[curPos] + 'api/tx/send';
fetch(url, {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: `{"rawtx":"${signedTxHash}"}`
}).then(response => {
if (response.ok)
response.json().then(data => resolve(data.txid.result));
else
response.text().then(data => resolve(data));
}).catch(error => reject(error));
})
}
floBlockchainAPI.getTx = function (txid) {
return new Promise((resolve, reject) => {
promisedAPI(`api/tx/${txid}`)
.then(response => resolve(response))
.catch(error => reject(error))
})
}
//Read Txs of Address between from and to
const readTxs = floBlockchainAPI.readTxs = function (addr, from, to) {
return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=${from}&to=${to}`)
.then(response => resolve(response))
.catch(error => reject(error))
});
}
//Read All Txs of Address (newest first)
floBlockchainAPI.readAllTxs = function (addr) {
return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${response.totalItems}0`)
.then(response => resolve(response.items))
.catch(error => reject(error));
}).catch(error => reject(error))
});
}
/*Read flo Data from txs of given Address
options can be used to filter data
limit : maximum number of filtered data (default = 1000, negative = no limit)
ignoreOld : ignore old txs (default = 0)
sentOnly : filters only sent data
receivedOnly: filters only received data
pattern : filters data that with JSON pattern
filter : custom filter funtion for floData (eg . filter: d => {return d[0] == '$'})
tx : (boolean) resolve tx data or not (resolves an Array of Object with tx details)
sender : flo-id(s) of sender
receiver : flo-id(s) of receiver
*/
floBlockchainAPI.readData = function (addr, options = {}) {
options.limit = options.limit || 0;
options.ignoreOld = options.ignoreOld || 0;
if (typeof options.senders === "string") options.senders = [options.senders];
if (typeof options.receivers === "string") options.receivers = [options.receivers];
return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
var newItems = response.totalItems - options.ignoreOld;
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${newItems * 2}`).then(response => {
if (options.limit <= 0)
options.limit = response.items.length;
var filteredData = [];
let numToRead = response.totalItems - options.ignoreOld,
unconfirmedCount = 0;
for (let i = 0; i < numToRead && filteredData.length < options.limit; i++) {
if (!response.items[i].confirmations) { //unconfirmed transactions
unconfirmedCount++;
if (numToRead < response.items[i].length)
numToRead++;
continue;
}
if (options.pattern) {
try {
let jsonContent = JSON.parse(response.items[i].floData);
if (!Object.keys(jsonContent).includes(options.pattern))
continue;
} catch (error) {
continue;
}
}
if (options.sentOnly) {
let flag = false;
for (let vin of response.items[i].vin)
if (vin.addr === addr) {
flag = true;
break;
}
if (!flag) continue;
}
if (Array.isArray(options.senders)) {
let flag = false;
for (let vin of response.items[i].vin)
if (options.senders.includes(vin.addr)) {
flag = true;
break;
}
if (!flag) continue;
}
if (options.receivedOnly) {
let flag = false;
for (let vout of response.items[i].vout)
if (vout.scriptPubKey.addresses[0] === addr) {
flag = true;
break;
}
if (!flag) continue;
}
if (Array.isArray(options.receivers)) {
let flag = false;
for (let vout of response.items[i].vout)
if (options.receivers.includes(vout.scriptPubKey.addresses[0])) {
flag = true;
break;
}
if (!flag) continue;
}
if (options.filter && !options.filter(response.items[i].floData))
continue;
if (options.tx) {
let d = {}
d.txid = response.items[i].txid;
d.time = response.items[i].time;
d.blockheight = response.items[i].blockheight;
d.senders = new Set(response.items[i].vin.map(v => v.addr));
d.receivers = new Set(response.items[i].vout.map(v => v.scriptPubKey.addresses[0]));
d.data = response.items[i].floData;
filteredData.push(d);
} else
filteredData.push(response.items[i].floData);
}
resolve({
totalTxs: response.totalItems - unconfirmedCount,
data: filteredData
});
}).catch(error => {
reject(error);
});
}).catch(error => {
reject(error);
});
});
}
})('object' === typeof module ? module.exports : window.floBlockchainAPI = {});

442
scripts/floCrypto.js Normal file
View File

@ -0,0 +1,442 @@
(function (EXPORTS) { //floCrypto v2.3.3e
/* FLO Crypto Operators */
'use strict';
const floCrypto = EXPORTS;
const p = BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
const ecparams = EllipticCurve.getSECCurveByName("secp256k1");
const ascii_alternatives = ` '\n '\n“ "\n” "\n --\n— ---\n≥ >=\n≤ <=\n≠ !=\n× *\n÷ /\n← <-\n→ ->\n↔ <->\n⇒ =>\n⇐ <=\n⇔ <=>`;
const exponent1 = () => p.add(BigInteger.ONE).divide(BigInteger("4"));
coinjs.compressed = true; //defaulting coinjs compressed to true;
function calculateY(x) {
let exp = exponent1();
// x is x value of public key in BigInteger format without 02 or 03 or 04 prefix
return x.modPow(BigInteger("3"), p).add(BigInteger("7")).mod(p).modPow(exp, p)
}
function getUncompressedPublicKey(compressedPublicKey) {
// Fetch x from compressedPublicKey
let pubKeyBytes = Crypto.util.hexToBytes(compressedPublicKey);
const prefix = pubKeyBytes.shift() // remove prefix
let prefix_modulus = prefix % 2;
pubKeyBytes.unshift(0) // add prefix 0
let x = new BigInteger(pubKeyBytes)
let xDecimalValue = x.toString()
// Fetch y
let y = calculateY(x);
let yDecimalValue = y.toString();
// verify y value
let resultBigInt = y.mod(BigInteger("2"));
let check = resultBigInt.toString() % 2;
if (prefix_modulus !== check)
yDecimalValue = y.negate().mod(p).toString();
return {
x: xDecimalValue,
y: yDecimalValue
};
}
function getSenderPublicKeyString() {
let privateKey = ellipticCurveEncryption.senderRandom();
var senderPublicKeyString = ellipticCurveEncryption.senderPublicString(privateKey);
return {
privateKey: privateKey,
senderPublicKeyString: senderPublicKeyString
}
}
function deriveSharedKeySender(receiverPublicKeyHex, senderPrivateKey) {
let receiverPublicKeyString = getUncompressedPublicKey(receiverPublicKeyHex);
var senderDerivedKey = ellipticCurveEncryption.senderSharedKeyDerivation(
receiverPublicKeyString.x, receiverPublicKeyString.y, senderPrivateKey);
return senderDerivedKey;
}
function deriveSharedKeyReceiver(senderPublicKeyString, receiverPrivateKey) {
return ellipticCurveEncryption.receiverSharedKeyDerivation(
senderPublicKeyString.XValuePublicString, senderPublicKeyString.YValuePublicString, receiverPrivateKey);
}
function getReceiverPublicKeyString(privateKey) {
return ellipticCurveEncryption.receiverPublicString(privateKey);
}
function wifToDecimal(pk_wif, isPubKeyCompressed = false) {
let pk = Bitcoin.Base58.decode(pk_wif)
pk.shift()
pk.splice(-4, 4)
//If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01).
if (isPubKeyCompressed == true) pk.pop()
pk.unshift(0)
let privateKeyDecimal = BigInteger(pk).toString()
let privateKeyHex = Crypto.util.bytesToHex(pk)
return {
privateKeyDecimal: privateKeyDecimal,
privateKeyHex: privateKeyHex
}
}
//generate a random Interger within range
floCrypto.randInt = function (min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(securedMathRandom() * (max - min + 1)) + min;
}
//generate a random String within length (options : alphaNumeric chars only)
floCrypto.randString = function (length, alphaNumeric = true) {
var result = '';
var characters = alphaNumeric ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' :
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():';
for (var i = 0; i < length; i++)
result += characters.charAt(Math.floor(securedMathRandom() * characters.length));
return result;
}
//Encrypt Data using public-key
floCrypto.encryptData = function (data, receiverPublicKeyHex) {
var senderECKeyData = getSenderPublicKeyString();
var senderDerivedKey = deriveSharedKeySender(receiverPublicKeyHex, senderECKeyData.privateKey);
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
let secret = Crypto.AES.encrypt(data, senderKey);
return {
secret: secret,
senderPublicKeyString: senderECKeyData.senderPublicKeyString
};
}
//Decrypt Data using private-key
floCrypto.decryptData = function (data, privateKeyHex) {
var receiverECKeyData = {};
if (typeof privateKeyHex !== "string") throw new Error("No private key found.");
let privateKey = wifToDecimal(privateKeyHex, true);
if (typeof privateKey.privateKeyDecimal !== "string") throw new Error("Failed to detremine your private key.");
receiverECKeyData.privateKey = privateKey.privateKeyDecimal;
var receiverDerivedKey = deriveSharedKeyReceiver(data.senderPublicKeyString, receiverECKeyData.privateKey);
let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue;
let decryptMsg = Crypto.AES.decrypt(data.secret, receiverKey);
return decryptMsg;
}
//Sign data using private-key
floCrypto.signData = function (data, privateKeyHex) {
var key = new Bitcoin.ECKey(privateKeyHex);
var messageHash = Crypto.SHA256(data);
var messageSign = Bitcoin.ECDSA.sign(messageHash, key.priv);
var sighex = Crypto.util.bytesToHex(messageSign);
return sighex;
}
//Verify signatue of the data using public-key
floCrypto.verifySign = function (data, signatureHex, publicKeyHex) {
var msgHash = Crypto.SHA256(data);
var sigBytes = Crypto.util.hexToBytes(signatureHex);
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
var verify = Bitcoin.ECDSA.verify(msgHash, sigBytes, publicKeyPoint);
return verify;
}
//Generates a new flo ID and returns private-key, public-key and floID
const generateNewID = floCrypto.generateNewID = function () {
var key = new Bitcoin.ECKey(false);
key.setCompressed(true);
return {
floID: key.getBitcoinAddress(),
pubKey: key.getPubKeyHex(),
privKey: key.getBitcoinWalletImportFormat()
}
}
Object.defineProperties(floCrypto, {
newID: {
get: () => generateNewID()
},
tmpID: {
get: () => {
let bytes = Crypto.util.randomBytes(20);
bytes.unshift(bitjs.pub);
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
asBytes: true
}), {
asBytes: true
});
var checksum = hash.slice(0, 4);
return bitjs.Base58.encode(bytes.concat(checksum));
}
}
});
//Returns public-key from private-key
floCrypto.getPubKeyHex = function (privateKeyHex) {
if (!privateKeyHex)
return null;
var key = new Bitcoin.ECKey(privateKeyHex);
if (key.priv == null)
return null;
key.setCompressed(true);
return key.getPubKeyHex();
}
//Returns flo-ID from public-key or private-key
floCrypto.getFloID = function (keyHex) {
if (!keyHex)
return null;
try {
var key = new Bitcoin.ECKey(keyHex);
if (key.priv == null)
key.setPub(keyHex);
return key.getBitcoinAddress();
} catch {
return null;
}
}
floCrypto.getAddress = function (privateKeyHex, strict = false) {
if (!privateKeyHex)
return;
var key = new Bitcoin.ECKey(privateKeyHex);
if (key.priv == null)
return null;
key.setCompressed(true);
let pubKey = key.getPubKeyHex(),
version = bitjs.Base58.decode(privateKeyHex)[0];
switch (version) {
case coinjs.priv: //BTC
return coinjs.bech32Address(pubKey).address;
case bitjs.priv: //FLO
return bitjs.pubkey2address(pubKey);
default:
return strict ? false : bitjs.pubkey2address(pubKey); //default to FLO address (if strict=false)
}
}
//Verify the private-key for the given public-key or flo-ID
floCrypto.verifyPrivKey = function (privateKeyHex, pubKey_floID, isfloID = true) {
if (!privateKeyHex || !pubKey_floID)
return false;
try {
var key = new Bitcoin.ECKey(privateKeyHex);
if (key.priv == null)
return false;
key.setCompressed(true);
if (isfloID && pubKey_floID == key.getBitcoinAddress())
return true;
else if (!isfloID && pubKey_floID == key.getPubKeyHex())
return true;
else
return false;
} catch {
return null;
}
}
//Check if the given flo-id is valid or not
floCrypto.validateFloID = function (floID) {
if (!floID)
return false;
try {
let addr = new Bitcoin.Address(floID);
return true;
} catch {
return false;
}
}
//Check if the given address (any blockchain) is valid or not
floCrypto.validateAddr = function (address, std = true, bech = true) {
let raw = decodeAddress(address);
if (!raw)
return false;
if (typeof raw.version !== 'undefined') { //legacy or segwit
if (std == false)
return false;
else if (std === true || (!Array.isArray(std) && std === raw.version) || (Array.isArray(std) && std.includes(raw.version)))
return true;
else
return false;
} else if (typeof raw.bech_version !== 'undefined') { //bech32
if (bech === false)
return false;
else if (bech === true || (!Array.isArray(bech) && bech === raw.bech_version) || (Array.isArray(bech) && bech.includes(raw.bech_version)))
return true;
else
return false;
} else //unknown
return false;
}
//Check the public-key for the address (any blockchain)
floCrypto.verifyPubKey = function (pubKeyHex, address) {
let raw = decodeAddress(address),
pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), {
asBytes: true
})));
return raw ? pub_hash === raw.hex : false;
}
//Convert the given address (any blockchain) to equivalent floID
floCrypto.toFloID = function (address, options = null) {
if (!address)
return;
let raw = decodeAddress(address);
if (!raw)
return;
else if (options) {
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
return;
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
return;
}
raw.bytes.unshift(bitjs.pub);
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
asBytes: true
}), {
asBytes: true
});
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
}
//Checks if the given addresses (any blockchain) are same (w.r.t keys)
floCrypto.isSameAddr = function (addr1, addr2) {
if (!addr1 || !addr2)
return;
let raw1 = decodeAddress(addr1),
raw2 = decodeAddress(addr2);
if (!raw1 || !raw2)
return false;
else
return raw1.hex === raw2.hex;
}
const decodeAddress = floCrypto.decodeAddr = function (address) {
if (!address)
return;
else if (address.length == 33 || address.length == 34) { //legacy encoding
let decode = bitjs.Base58.decode(address);
let bytes = decode.slice(0, decode.length - 4);
let checksum = decode.slice(decode.length - 4),
hash = Crypto.SHA256(Crypto.SHA256(bytes, {
asBytes: true
}), {
asBytes: true
});
return (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) ? null : {
version: bytes.shift(),
hex: Crypto.util.bytesToHex(bytes),
bytes
}
} else if (address.length == 42) { //bech encoding
let decode = coinjs.bech32_decode(address);
if (decode) {
let bytes = decode.data;
let bech_version = bytes.shift();
bytes = coinjs.bech32_convert(bytes, 5, 8, false);
return {
bech_version,
hrp: decode.hrp,
hex: Crypto.util.bytesToHex(bytes),
bytes
}
} else
return null;
}
}
//Split the str using shamir's Secret and Returns the shares
floCrypto.createShamirsSecretShares = function (str, total_shares, threshold_limit) {
try {
if (str.length > 0) {
var strHex = shamirSecretShare.str2hex(str);
var shares = shamirSecretShare.share(strHex, total_shares, threshold_limit);
return shares;
}
return false;
} catch {
return false
}
}
//Returns the retrived secret by combining the shamirs shares
const retrieveShamirSecret = floCrypto.retrieveShamirSecret = function (sharesArray) {
try {
if (sharesArray.length > 0) {
var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length));
comb = shamirSecretShare.hex2str(comb);
return comb;
}
return false;
} catch {
return false;
}
}
//Verifies the shares and str
floCrypto.verifyShamirsSecret = function (sharesArray, str) {
if (!str)
return null;
else if (retrieveShamirSecret(sharesArray) === str)
return true;
else
return false;
}
const validateASCII = floCrypto.validateASCII = function (string, bool = true) {
if (typeof string !== "string")
return null;
if (bool) {
let x;
for (let i = 0; i < string.length; i++) {
x = string.charCodeAt(i);
if (x < 32 || x > 127)
return false;
}
return true;
} else {
let x, invalids = {};
for (let i = 0; i < string.length; i++) {
x = string.charCodeAt(i);
if (x < 32 || x > 127)
if (x in invalids)
invalids[string[i]].push(i)
else
invalids[string[i]] = [i];
}
if (Object.keys(invalids).length)
return invalids;
else
return true;
}
}
floCrypto.convertToASCII = function (string, mode = 'soft-remove') {
let chars = validateASCII(string, false);
if (chars === true)
return string;
else if (chars === null)
return null;
let convertor, result = string,
refAlt = {};
ascii_alternatives.split('\n').forEach(a => refAlt[a[0]] = a.slice(2));
mode = mode.toLowerCase();
if (mode === "hard-unicode")
convertor = (c) => `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
else if (mode === "soft-unicode")
convertor = (c) => refAlt[c] || `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
else if (mode === "hard-remove")
convertor = c => "";
else if (mode === "soft-remove")
convertor = c => refAlt[c] || "";
else
return null;
for (let c in chars)
result = result.replaceAll(c, convertor(c));
return result;
}
floCrypto.revertUnicode = function (string) {
return string.replace(/\\u[\dA-F]{4}/gi,
m => String.fromCharCode(parseInt(m.replace(/\\u/g, ''), 16)));
}
})('object' === typeof module ? module.exports : window.floCrypto = {});

9356
scripts/lib.js Normal file

File diff suppressed because it is too large Load Diff