Skip to content

Commit

Permalink
Merge pull request #68 from Takadenoshi/sign-json-ledger
Browse files Browse the repository at this point in the history
Sign JSON transactions with ledger & Search for Ledger Keys Pro
  • Loading branch information
Takadenoshi authored Oct 19, 2023
2 parents 81277e8 + fffde0e commit 55d53e0
Show file tree
Hide file tree
Showing 5 changed files with 856 additions and 3 deletions.
3 changes: 1 addition & 2 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,14 @@
<h1>Kadena Coin Transfer</h1>
<div>
<a href="transfer-create.html"><button id="submit-button" class="ui primary button">Transfer</button></a>
<a href="transfer-ledger-create.html"><button id="transfer-button" class="ui primary button">Transfer With Ledger</button></a>
<a href="ledger.html"><button id="transfer-button" class="ui primary button">Ledger Tools</button></a>
<a href="xchain.html">
<button id="xchain-button" class="ui primary button">Finish Cross Chain Transfer</button>
</a>
</div>
<div style="margin-top:10px;">
<a href="https://balance.chainweb.com"><button class="ui primary button">Check Account Balance</button></a>
<button id="gen-button" class="ui primary button">Generate KeyPair (saves to file)</button>
<!-- <a href="address.html"><button id="submit-button" class="ui primary button">Generate Kadena Address</button></a> -->
</div>
</div>
</body>
Expand Down
284 changes: 284 additions & 0 deletions docs/ledger-keys.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
<html>
<head>
<title>Kadena Transfer Tool</title>
<link rel="icon" href="https://explorer.chainweb.com/static/img/favicon/0d63dkd822sxczcy51lbhqgflzdxw6lhgf88x2j7kc55hcpk120y-favicon-96x96.png">
<script src="https://code.jquery.com/jquery-3.1.1.min.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
<script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>
<script src="../util/ledger-os.js"></script>
<script src="../util/httptransp.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<script>
window.addEventListener('load', function (event) {
document.getElementById("search-specific-button").addEventListener("click", () => toggleSearch('specific'));
document.getElementById("search-prefix-button").addEventListener("click", () => toggleSearch('prefix'));
});

let searching = false;
function toggleSearch(name) {
if (!searching) return start(name);
const wasSearching = searching;
stop(searching);
if (wasSearching === name) return;
setTimeout(() => toggleSearch(name), 1100);
}

function start(name) {
switch(name) {
case 'specific': return startSearchSpecific();
case 'prefix': return startSearchPrefix();
default: console.log('Unknown job to start:', name);
}
}

function stop(name) {
switch(name) {
case 'specific': return stopSearchSpecific();
case 'prefix': return stopSearchPrefix();
default: console.log('Unknown job to stop:', name);
}
}

function toggleSearchPrefix() {
if (searching) {
stopSearchPrefix();
} else {
startSearchPrefix();
}
}

function startSearchPrefix() {
const input = document.getElementById("search-prefix");
const keys = validatePrefix(input.value);
if(keys.length) {
const btn = document.getElementById("search-prefix-button");
btn.innerText = 'Stop';
input.disabled = true;
search('prefix', keys.join(', '), publicKey => keys.some(key => publicKey.startsWith(key)), false);
}
}

function stopSearchPrefix() {
searching = false;
reset();
const elem = document.getElementById('prefix-status');
elem.innerHTML = elem.innerHTML.replace('Searching for:', 'Stopped search for:');
elem.innerHTML = elem.innerHTML.replace('Looking in', 'Reached');
}

function startSearchSpecific() {
const input = document.getElementById("search-key");
const key = validateKey(input.value);
if (key) {
const btn = document.getElementById("search-specific-button");
btn.innerText = 'Stop';
input.disabled = true;
search('specific', key, publicKey => publicKey === key);
} else {
setStatus('specific', 'Error: Invalid key');
}
}

const BIP32_PATH_PREFIX = "m/44'/626'/0'/0/";
function getPath(keyId) {
return `${BIP32_PATH_PREFIX}${keyId}"`;
}

function bufferToHex (buffer) {
return [...new Uint8Array (buffer)]
.map (b => b.toString (16).padStart (2, "0"))
.join ("");
}

async function getLedger(){
if (!window.ledger) {
var transp = await window.TranspWeb.create();
window.ledger = new window.Kadena(transp);
}
}

function copyPrefixResults(elem) {
const str = res.map(([n, key]) => `Key ${n}: ${key}`).join('\n');
navigator.clipboard.writeText(str);
elem.classList.add('green');
const icon = elem.childNodes[0];
icon.classList.remove("copy");
icon.classList.add("checkmark");
}

let res = [];
async function search(name, key, pred, once = true) {
searching = name;
let i = -1;
await getLedger();
let suffix = '';
while(searching) {
i++;
try {
const shortKey = key.slice(0, 6)+'..'+key.slice(-6);
setStatus(name, `Searching for:<br/>${key}<br/><br/>Looking in slot: ${i}<div class="rel">${suffix}</div>`, suffix ? 'green' : 'yellow');
const publicKey = bufferToHex((await window.ledger.getPublicKey(getPath(i))).publicKey);
// not the key we are looking for. continue
if (!pred(publicKey)) continue;
// found and we only care about one key. finish it
if (once) {
searching = false;
setStatus(name, `<strong>Found</strong><br/><strong>Slot: ${i}</strong><br/>Path: ${getPath(i)}<br/>Key: ${publicKey}`, 'green');
reset();
break;
}
// found but we keep looking
suffix = suffix ? suffix : '<div class="ui divider"></div><strong style="text-decoration: underline">Found</strong><br/><button onclick="copyPrefixResults(this)" type="button" id="copybtn" class="circular topright ui icon button"><i class="icon copy"></i></button>';
suffix += `<strong>Slot ${i}</strong> (${getPath(i)})<br/>${publicKey}<br/><br/>`;
res.push([i, publicKey]);
} catch(e) {
searching = false;
console.error(e);
setStatus(name, 'Error: '+e.message);
reset();
}
}
}

function stopSearchSpecific() {
searching = false;
setStatus('specific');
reset();
}

function reset() {
[['search-specific-button', 'search-key'], ['search-prefix-button', 'search-prefix']].forEach(([btnId, inputId]) => {
const btn = document.getElementById(btnId);
btn.innerText = 'Search';
const input = document.getElementById(inputId);
input.disabled = false;
});
}

function setStatus(name, value, cl) {
const elem = document.getElementById(name+'-status');
['yellow', 'red', 'green'].forEach(c => elem.classList.remove(c));
if (cl)
elem.classList.add(cl);
else
elem.classList.add('red');
if (value) {
elem.style.display='block';
elem.innerHTML = value;
} else {
elem.style.display='none';
elem.innerHTML = '';
}
}

function validatePrefix(input = '') {
input = input.split(',').map(word => word.trim().toLowerCase().replace(/^k:/, '')).filter(Boolean);
const hexRegex = /^[0-9a-f]+$/;
const invalid = input.filter(word => !hexRegex.test(word));
if (invalid.length) {
setStatus('prefix', `Error: Invalid prefixes: ${invalid.join(', ')}`);
return;
}
return input;
}

function validateKey(input = '') {
input = input.trim().toLowerCase();
if (input.startsWith('k:')) {
input = input.slice(2);
}
return input.length === 64 && /[0-9a-f]+/.test(input) && input;
}

function prefixTemplate(name) {
if (searching === 'prefix')
return;
const input = document.getElementById("search-prefix");
input.value = name === 'all' ? Object.values(templates).flat().join(',') : templates[name].join(',');
}

const templates = {
"2": [ '000', '111', '222', '333', '444', '555', '666', '777', '888', '999', 'aaa', 'bbb', 'ccc', 'ddd', 'eee', 'fff' ],
"3": ["aba", "ace", "add", "aff", "baa", "bad", "bed", "bee", "cab", "cad", "cee", "dab", "dad", "deb", "dee", "ebb", "eff", "fad", "fed", "fee",],
"4": ["abba", "abbe", "abed", "aced", "baba", "babe", "bade", "baff", "bead", "beef", "caca", "cade", "cafe", "caff", "ceca", "cede", "dace", "dada", "daff", "dead", "deaf", "deed", "face", "fade", "feed"],
};

</script>
</head>
<body>
<div id="main">
<div class="ui container">
<img src="https://explorer.chainweb.com/static/1lv9xhxyhlqc262kffl55w08ms1cvxsnrv49zhvm0b799dsi0v0i-kadena-k-logo.png" class="center" style="height:70px">
<h1>Kadena Search Ledger Keys</h1>

<form id="kadena-form" class="ui form">
<div class="ui segment">
<h4 class="ui header">Search for key (exact)</h4>
<div class="field">
<div class="ui action input">
<input placeholder="Account to search for" id="search-key" class="ui input"></input>
<button type="button" id="search-specific-button" class="ui right button">Search</button>
</div>
</div>
<div class="ui raised segment" style="display: none" id="specific-status"></div>
</div>

<div class="ui segment">
<h4 class="ui header">Search for key prefix</h4>
<div class="field">
<div class="ui action input">
<input placeholder="Prefix(es) to search for" id="search-prefix" class="ui input"></input>
<button type="button" id="search-prefix-button" class="ui right button">Search</button>
</div>
</div>
<p>Tip: You can search for multiple prefixes by separating them with comma (,)<br/>
Templates:
<a onclick="prefixTemplate('2')">3-identical</a>
<a onclick="prefixTemplate('3')">3-word</a>
<a onclick="prefixTemplate('4')">4-word</a>
<a onclick="prefixTemplate('all')">All</a>
</p>
<div class="ui raised segment" style="display: none" id="prefix-status"></div>
</div>
</form>

</div>
</body>
<style>
.container {
margin-top: 40px;
text-align: center;
padding-bottom: 50px;
}

#kadena-form {
margin: auto;
text-align: center;
max-width: 640px;
}
a:hover {
cursor:pointer;
}
.ui.dimmer {
background-color: rgba(0,0,0,.50);
}
.ui.dimmer .content {
background-color: rgba(0,0,0,.9);
padding: 7px;
padding-left: 10px;
padding-right: 10px;
}
.rel {
position: relative;
}
.segment {
word-break: break-word;
}
.topright {
position: absolute;
top: 5px;
right: 0;
}
</style>
</html>
73 changes: 73 additions & 0 deletions docs/ledger.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<html>
<head>
<title>Kadena Transfer Tool</title>
<script src="https://code.jquery.com/jquery-3.1.1.min.js" crossorigin="anonymous"></script>
<link rel="icon" href="https://explorer.chainweb.com/static/img/favicon/0d63dkd822sxczcy51lbhqgflzdxw6lhgf88x2j7kc55hcpk120y-favicon-96x96.png">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
<script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pact-lang-api@4.1.2/pact-lang-api-global.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<script>
function download(data, filename, type) {
var file = new Blob([data], {type: type});
if (window.navigator.msSaveOrOpenBlob) // IE10+
window.navigator.msSaveOrOpenBlob(file, filename);
else { // Others
var a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
}

document.addEventListener('click', async function (event) {
if (!event.target.matches('#gen-button')) return;
event.preventDefault();
var kp = Pact.crypto.genKeyPair();
var id = kp.publicKey.substring(0, 6);
var priv = `public: ${kp.publicKey}\nsecret: ${kp.secretKey}`
var privName = `private-keypair-${id}.kda`
var pub = `public: ${kp.publicKey}`
var pubName = `public-keypair-${id}.kda`
download(priv, privName, "text/plain;charset=utf-8");
download(pub, pubName, "text/plain;charset=utf-8");
}, false);

</script>
</head>
<body>
<div id="main">
<div class="container">
<img src="https://explorer.chainweb.com/static/1lv9xhxyhlqc262kffl55w08ms1cvxsnrv49zhvm0b799dsi0v0i-kadena-k-logo.png" class="center" style="height:70px">
<h1>Kadena Ledger Tools</h1>
<div style="display: flex; justify-content: center">
<div style="display: flex; flex-direction: column; width: fit-content" class="buttons">
<a href="transfer-ledger-create.html"><button id="submit-button" class="ui primary button">Simple Transfer</button></a>
<a href="sign-ledger-json.html"><button id="transfer-button" class="ui primary button">Sign Transactions (JSON)</button></a>
<a href="ledger-keys.html"><button id="xchain-button" class="ui primary button">Search Ledger Keys</button></a>
</div>
</div>
</div>
</body>
<style>

.container {
text-align: center;
margin-top: 20px;
}
.field {
margin-bottom: 20px;
}
.buttons button {
width: 100%;
margin-bottom: 4px !important;
}
</style>
</html>
Loading

0 comments on commit 55d53e0

Please sign in to comment.