dynamic certificate generation
This commit is contained in:
parent
45b010aaf1
commit
2f74539d36
99
css/main.css
99
css/main.css
@ -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
2
css/main.min.css
vendored
@ -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}}
|
||||
111
css/main.scss
111
css/main.scss
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
405
index.html
405
index.html
@ -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
580
scripts/floBlockchainAPI.js
Normal 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
442
scripts/floCrypto.js
Normal 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
9356
scripts/lib.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user