Skip to content
This repository was archived by the owner on May 15, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions browser-demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html>
<head>
<title>TipLink inside browser demo</title>
</head>
<body>
<button id="create_tiplink_btn">Create TipLink</button>
<div id="tiplink_container"></div>

<script src="browser-js/main.js"></script>
<script src="browser-js/base58.js"></script>
<script src="browser-js/sodium.js"></script>
<script src="https://unpkg.com/@solana/web3.js@1.78.5/lib/index.iife.js"></script>
</body>
</html>
73 changes: 73 additions & 0 deletions browser-js/base58.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const base58_chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

const create_base58_map = () => {
const base58M = Array(256).fill(-1);
for (let i = 0; i < base58_chars.length; ++i)
base58M[base58_chars.charCodeAt(i)] = i;

return base58M;
};

function base58_to_binary(base58String) {
if (!base58String || typeof base58String !== "string")
throw new Error(`Expected base58 string but got “${base58String}”`);
if (base58String.match(/[IOl0]/gmu))
throw new Error(
`Invalid base58 character “${base58String.match(/[IOl0]/gmu)}”`
);
const lz = base58String.match(/^1+/gmu);
const psz = lz ? lz[0].length : 0;
const size =
((base58String.length - psz) * (Math.log(58) / Math.log(256)) + 1) >>> 0;

return new Uint8Array([
...new Uint8Array(psz),
...base58String
.match(/.{1}/gmu)
.map((i) => base58_chars.indexOf(i))
.reduce((acc, i) => {
acc = acc.map((j) => {
const x = j * 58 + i;
i = x >> 8;
return x;
});
return acc;
}, new Uint8Array(size))
.reverse()
.filter(
(
(lastValue) => (value) =>
// @ts-ignore
(lastValue = lastValue || value)
)(false)
),
]);
}

const base58Map = create_base58_map();

function binary_to_base58(uint8array) {
const result = [];

for (const byte of uint8array) {
let carry = byte;
for (let j = 0; j < result.length; ++j) {
// @ts-ignore
const x = (base58Map[result[j]] << 8) + carry;
result[j] = base58_chars.charCodeAt(x % 58);
carry = (x / 58) | 0;
}
while (carry) {
result.push(base58_chars.charCodeAt(carry % 58));
carry = (carry / 58) | 0;
}
}

for (const byte of uint8array)
if (byte) break;
else result.push("1".charCodeAt(0));

result.reverse();

return String.fromCharCode(...result);
}
83 changes: 83 additions & 0 deletions browser-js/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
window.sodium = {
onload: function (sodium) {

/* --------- Put your custom code here --------- */

let tiplinkIndex = 1;

document.getElementById('create_tiplink_btn').addEventListener("click", () => createTipLink());

async function createTipLink() {
const link = await TipLink.create();

document.getElementById('tiplink_container').innerHTML += '<a target="_blank" href="' + link.url.toString() + '">Go to TipLink ' + tiplinkIndex + '</a><br>';

tiplinkIndex++;
}

/* --------- End of custom code --------- */

var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const DEFAULT_TIPLINK_KEYLENGTH = 12;
const TIPLINK_ORIGIN = "https://tiplink.io";
const TIPLINK_PATH = "/i";
const kdf = (fullLength, pwShort, salt) => __awaiter(void 0, void 0, void 0, function* () {
return sodium.crypto_pwhash(fullLength, pwShort, salt, sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, sodium.crypto_pwhash_ALG_DEFAULT);
});
const randBuf = (l) => __awaiter(void 0, void 0, void 0, function* () {
return sodium.randombytes_buf(l);
});
const kdfz = (fullLength, pwShort) => __awaiter(void 0, void 0, void 0, function* () {
const salt = new Uint8Array(sodium.crypto_pwhash_SALTBYTES);
return yield kdf(fullLength, pwShort, salt);
});
const pwToKeypair = (pw) => __awaiter(void 0, void 0, void 0, function* () {
const seed = yield kdfz(sodium.crypto_sign_SEEDBYTES, pw);
return (solanaWeb3.Keypair.fromSeed(seed));
});
class TipLink {
constructor(url, keypair) {
this.url = url;
this.keypair = keypair;
}
static create() {
return __awaiter(this, void 0, void 0, function* () {
const b = yield randBuf(DEFAULT_TIPLINK_KEYLENGTH);
const keypair = yield pwToKeypair(b);
const hash = (0, binary_to_base58)(b);
const urlString = `${TIPLINK_ORIGIN}${TIPLINK_PATH}#${hash}`;
// can't assign hash as it causes an error in React Native
const link = new URL(urlString);
const tiplink = new TipLink(link, keypair);
return tiplink;
});
}
static fromUrl(url) {
return __awaiter(this, void 0, void 0, function* () {
const slug = url.hash.slice(1);
const pw = Uint8Array.from((0, base58_to_binary)(slug));
const keypair = yield pwToKeypair(pw);
const tiplink = new TipLink(url, keypair);
return tiplink;
});
}
static fromLink(link) {
return __awaiter(this, void 0, void 0, function* () {
const url = new URL(link);
return this.fromUrl(url);
});
}
}
}
}
1 change: 1 addition & 0 deletions browser-js/sodium.js

Large diffs are not rendered by default.