-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
392 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>Aleo Testnet2 Tools</title> | ||
<link | ||
rel="stylesheet" | ||
href="https://cdn.jsdelivr.net/npm/@picocss/[email protected]/css/pico.classless.min.css" | ||
/> | ||
<style> | ||
.hidden { | ||
display: none; | ||
} | ||
input[readonly] { | ||
opacity: 0.6; | ||
cursor: copy; | ||
} | ||
label { | ||
font-weight: bold; | ||
transition: color 0.2s ease; | ||
} | ||
label:has(input[aria-invalid='true']) { | ||
color: var(--pico-form-element-invalid-border-color); | ||
} | ||
label:has(input[aria-invalid='true']:focus) { | ||
color: var(--pico-form-element-invalid-active-border-color); | ||
} | ||
label:has(input[aria-invalid='false']) { | ||
color: var(--pico-form-element-valid-border-color); | ||
} | ||
label:has(input[aria-invalid='false']:focus) { | ||
color: var(--pico-form-element-valid-active-border-color); | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<main class="hidden" id="wasmWarning"> | ||
<article> | ||
<header> | ||
<h3>Unable to load Web Assembly</h3> | ||
</header> | ||
<p> | ||
This page uses | ||
<a href="https://webassembly.org/" rel="noopener noreferer" | ||
>WebAssembly</a | ||
> | ||
to run Rust code in the browser. Your browser does not support | ||
WebAssembly, or something prevented it from loading. | ||
</p> | ||
</article> | ||
</main> | ||
<header> | ||
<h1>Aleo Testnet2 Tools</h1> | ||
<p> | ||
Sign and verify messages using your Testnet2 private key. Compare your | ||
Testnet2 address to your Mainnet address. | ||
</p> | ||
<h3>Table of Contents</h3> | ||
<ul> | ||
<li><a href="#sign">Sign a Message</a></li> | ||
<li><a href="#verify">Verify a Signature</a></li> | ||
<li><a href="#derive">Derive Addresses from a Private Key</a></li> | ||
<li><a href="#selfhost">Self Host this Site</a></li> | ||
</ul> | ||
</header> | ||
<main> | ||
<section> | ||
<h2 id="sign">Sign a Message</h2> | ||
<p> | ||
Enter a private key and a message to generate an Aleo Testnet2 | ||
compatible signature. | ||
</p> | ||
<form id="signForm"> | ||
<label for="sign_privateKey"> | ||
Testnet2 Private Key | ||
<input | ||
name="sign_privateKey" | ||
autocomplete="off" | ||
type="password" | ||
placeholder="Testnet2 Private Key" | ||
aria-label="Private Key" | ||
required | ||
/> | ||
</label> | ||
<label for="sign_message"> | ||
Message to Sign | ||
<input | ||
name="sign_message" | ||
autocomplete="off" | ||
type="text" | ||
placeholder="Message to Sign" | ||
aria-label="Message" | ||
required | ||
/> | ||
</label> | ||
<label for="sign_signature"> | ||
Signature | ||
<input | ||
name="sign_signature" | ||
autocomplete="off" | ||
type="text" | ||
placeholder="Signature" | ||
aria-label="Signature" | ||
readonly | ||
/> | ||
</label> | ||
</form> | ||
</section> | ||
<section> | ||
<h2 id="verify">Verify a Message</h2> | ||
<p> | ||
Enter an address, a message, and a Testnet2 signature verify | ||
authenticity. | ||
</p> | ||
<form id="verifyForm"> | ||
<label for="verify_address"> | ||
Testnet2 Address | ||
<input | ||
name="verify_address" | ||
autocomplete="off" | ||
type="text" | ||
placeholder="Testnet2 Address" | ||
aria-label="Address" | ||
required | ||
/> | ||
</label> | ||
<label for="verify_message"> | ||
Message to Verify | ||
<input | ||
name="verify_message" | ||
autocomplete="off" | ||
type="text" | ||
placeholder="Message to Verify" | ||
aria-label="Message" | ||
required | ||
/> | ||
</label> | ||
<label for="verify_signature"> | ||
Signature to Verify | ||
<input | ||
name="verify_signature" | ||
autocomplete="off" | ||
type="text" | ||
placeholder="Signature to Verify" | ||
aria-label="Signature" | ||
required | ||
/> | ||
</label> | ||
</form> | ||
</section> | ||
<section> | ||
<h2 id="derive">Derive Addresses from a Testnet2 Private Key</h2> | ||
<p> | ||
Enter an address, a message, and a Testnet2 signature verify | ||
authenticity. | ||
</p> | ||
<form id="deriveForm"> | ||
<label for="derive_privateKey"> | ||
Testnet2 Private Key | ||
<input | ||
name="derive_privateKey" | ||
autocomplete="off" | ||
type="password" | ||
placeholder="Testnet2 Private Key" | ||
aria-label="Private Key" | ||
required | ||
/> | ||
</label> | ||
<label for="derive_address2"> | ||
Derived Testnet2 Address | ||
<input | ||
name="derive_address2" | ||
autocomplete="off" | ||
type="text" | ||
placeholder="Testnet2 Address" | ||
aria-label="Testnet2 Address" | ||
readonly | ||
/> | ||
</label> | ||
<label for="derive_address"> | ||
Derived Mainnet Address | ||
<input | ||
name="derive_address" | ||
autocomplete="off" | ||
type="text" | ||
placeholder="Mainnet Address" | ||
aria-label="Mainnet Address" | ||
readonly | ||
/> | ||
</label> | ||
</form> | ||
</section> | ||
<section> | ||
<h2 id="selfhost">Self Host this Site</h2> | ||
<p> | ||
The source code for this website is available in the | ||
<a | ||
rel="noopener noreferer" | ||
href="https://github.com/monadicus/aleo-testnet2-tools" | ||
> | ||
monadicus/aleo-testnet2-tools | ||
</a> | ||
repository. | ||
</p> | ||
</section> | ||
</main> | ||
<footer> | ||
<small> | ||
<a | ||
rel="noopener noreferer" | ||
href="https://github.com/monadicus/aleo-testnet2-tools" | ||
>Source code</a | ||
> | ||
• | ||
<a rel="noopener noreferer" href="https://aleo.org/">Aleo</a> | ||
• | ||
<a rel="noopener noreferer" href="https://github.com/Aleonet/snarkOS" | ||
>snarkOS</a | ||
> | ||
• Built with <a href="https://picocss.com">Pico</a> | ||
</small> | ||
</footer> | ||
<script type="module" src="./script.mjs"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
import init, * as tools from './pkg/aleo_testnet2_wasm_suite.js'; | ||
|
||
/** query selector @returns {Element} */ | ||
const $ = document.querySelector.bind(document); | ||
/** query selector all @returns {Element[]} */ | ||
const $$ = q => Array.from(document.querySelectorAll(q)); | ||
|
||
/** | ||
* @param {Element} el | ||
* @param {boolean | null} isInvalid | ||
*/ | ||
function setInvalid(el, isInvalid = true) { | ||
if (isInvalid === null) { | ||
el.removeAttribute('aria-invalid'); | ||
return; | ||
} | ||
el.setAttribute('aria-invalid', isInvalid + ''); | ||
} | ||
|
||
/** @param {Element} el */ | ||
const clearInvalid = el => setInvalid(el, null); | ||
|
||
/** @typedef {({form: HTMLFormElement} | Record<string, HTMLInputElement>)} FormElements */ | ||
|
||
/** | ||
* get the inputs from a form | ||
* @param {string} formId | ||
* @returns {FormElements} | ||
*/ | ||
function formElems(formId) { | ||
const form = $('#' + formId); | ||
const elems = $$(`#${formId} input`); | ||
if (!form || elems.length === 0) return null; | ||
|
||
const entries = Object.fromEntries( | ||
elems.map(e => [e.getAttribute('name').split('_').slice(1).join('_'), e]) | ||
); | ||
|
||
return { form, ...entries }; | ||
} | ||
|
||
const debounce = (fn, duration = 250) => { | ||
let timeout; | ||
return function debounced() { | ||
clearTimeout(timeout); | ||
timeout = setTimeout(fn, duration); | ||
}; | ||
}; | ||
|
||
/** | ||
* @param {string} formId Form Id | ||
* @param {(inputs: Record<string, HTMLInputElement>) => {}} handler | ||
*/ | ||
function formHandler(formId, handler) { | ||
const { form, ...inputs } = formElems(formId); | ||
if (!inputs) { | ||
console.error('invalid selectors for form', formId); | ||
return; | ||
} | ||
|
||
const invoker = debounce(() => { | ||
for (const key in inputs) { | ||
/** @type {HTMLInputElement} */ | ||
const el = inputs[key]; | ||
clearInvalid(el); | ||
if (el.hasAttribute('readonly')) { | ||
el.value = ''; | ||
} | ||
} | ||
handler(inputs); | ||
}); | ||
|
||
for (const key in inputs) { | ||
/** @type {HTMLInputElement} */ | ||
const el = inputs[key]; | ||
clearInvalid(el); | ||
|
||
if (el.hasAttribute('readonly')) { | ||
el.setAttribute('data-tooltip', 'Click to copy to clipboard'); | ||
const copyDone = debounce(() => clearInvalid(el), 1000); | ||
|
||
// copy text to clipboard on click | ||
el.addEventListener('click', _event => { | ||
if (el.value.length === 0) return; | ||
|
||
el.select(); | ||
el.setSelectionRange(0, 99999); | ||
|
||
try { | ||
navigator.clipboard.writeText(el.value); | ||
setInvalid(el, false); | ||
} catch (err) { | ||
console.log('error copying text to clipboard', err); | ||
setInvalid(el); | ||
} | ||
copyDone(); | ||
}); | ||
} else { | ||
el.addEventListener('change', invoker); | ||
el.addEventListener('keyup', invoker); | ||
el.addEventListener('paste', invoker); | ||
} | ||
} | ||
} | ||
|
||
init() | ||
.then(() => { | ||
formHandler('signForm', ({ message, privateKey, signature }) => { | ||
if (privateKey.value.length === 0) return; | ||
|
||
try { | ||
tools.testnet2_address(privateKey.value); | ||
} catch (err) { | ||
setInvalid(privateKey); | ||
signature.value = err.toString(); | ||
return; | ||
} | ||
|
||
try { | ||
signature.value = tools.testnet2_sign(privateKey.value, message.value); | ||
} catch (err) { | ||
setInvalid(message); | ||
signature.value = err.toString(); | ||
return; | ||
} | ||
}); | ||
|
||
formHandler('verifyForm', ({ address, message, signature }) => { | ||
if (address.value.length === 0) return; | ||
if (!tools.check_testnet2_address(address.value)) { | ||
return setInvalid(address); | ||
} | ||
|
||
if (signature.value.length === 0) return; | ||
|
||
try { | ||
setInvalid( | ||
signature, | ||
!tools.testnet2_verify(address.value, message.value, signature.value) | ||
); | ||
} catch (err) { | ||
console.log('error verifying address:', err); | ||
setInvalid(signature); | ||
} | ||
}); | ||
|
||
formHandler('deriveForm', ({ privateKey, address, address2 }) => { | ||
if (privateKey.value.length === 0) return; | ||
|
||
try { | ||
address2.value = tools.testnet2_address(privateKey.value); | ||
} catch (err) { | ||
setInvalid(address2); | ||
address2.value = err.toString(); | ||
} | ||
|
||
try { | ||
address.value = tools.mainnet_address(privateKey.value); | ||
} catch (e) { | ||
setInvalid(address); | ||
address.value = e.toString(); | ||
} | ||
}); | ||
}) | ||
.catch(e => { | ||
console.log(e); | ||
$('#wasmWarning').classList.remove('hidden'); | ||
}); |