diff --git a/export/index.html b/export/index.html
index 23da3d6..296a777 100644
--- a/export/index.html
+++ b/export/index.html
@@ -17,7 +17,10 @@
display:inline-block;
width: 8em;
}
- input[type=text] {
+ form {
+ text-align: left;
+ }
+ input[type=text], select {
width: 40em;
margin: 0.5em;
font-family: 'Courier New', Courier, monospace;
@@ -81,16 +84,27 @@
Export Key Material
- Inject Export Bundle
+ Inject Key Export Bundle
The export bundle comes from the parent page and is composed of a public key and an encrypted payload. The payload is encrypted to this document's embedded key (stored in local storage and displayed above). The scheme relies on HPKE (RFC 9180).
+ Inject Wallet Export Bundle
+ The export bundle comes from the parent page and is composed of a public key and an encrypted payload. The payload is encrypted to this document's embedded key (stored in local storage and displayed above). The scheme relies on HPKE (RFC 9180).
@@ -268,12 +282,118 @@ Message log
}
/**
- * Returns a hex-encoded raw private key from private key bytes.
+ * Encodes a buffer into a base58-encoded string.
+ * @param {Uint8Array} bytes The buffer to encode.
+ * @return {string} The base58-encoded string.
+ */
+ function base58Encode(bytes) {
+ // See https://en.bitcoin.it/wiki/Base58Check_encoding
+ const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
+ let result = '';
+ let digits = [0];
+ for (let i = 0; i < bytes.length; i++) {
+ let carry = bytes[i];
+ for (let j = 0; j < digits.length; ++j) {
+ carry += digits[j] << 8;
+ digits[j] = carry % 58;
+ carry = (carry / 58) | 0;
+ }
+
+ while (carry > 0) {
+ digits.push(carry % 58);
+ carry = (carry / 58) | 0;
+ }
+ }
+ // Convert digits to a base58 string
+ for (let k = 0; k < digits.length; k++) {
+ result = alphabet[digits[k]] + result;
+ }
+
+ // Add '1' for each leading 0 byte
+ for (let i = 0; bytes[i] === 0 && i < bytes.length - 1; i++) {
+ result = '1' + result;
+ }
+ return result;
+ }
+
+ /**
+ * Decodes a base58-encoded string into a buffer
+ * This function throws an error when the string contains invalid characters.
+ * @param {string} s The base58-encoded string.
+ * @return {Uint8Array} The decoded buffer.
+ */
+ function base58Decode(s) {
+ // See https://en.bitcoin.it/wiki/Base58Check_encoding
+ var alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
+ var decoded = BigInt(0);
+ var decodedBytes = [];
+ var leadingZeros = [];
+ for (var i = 0; i < s.length; i++) {
+ if (alphabet.indexOf(s[i]) === -1) {
+ throw new Error(`cannot base58-decode: ${s[i]} isn't a valid character`)
+ }
+ var carry = alphabet.indexOf(s[i]);
+
+ // If the current base58 digit is 0, append a 0 byte.
+ // "i == leadingZeros.length" can only be true if we have not seen non-zero bytes so far.
+ // If we had seen a non-zero byte, carry wouldn't be 0, and i would be strictly more than `leadingZeros.length`
+ if (carry == 0 && i === leadingZeros.length) {
+ leadingZeros.push(0);
+ }
+
+ var j = 0;
+ while (j < decodedBytes.length || carry > 0) {
+ var currentByte = decodedBytes[j];
+
+ // shift the current byte 58 units and add the carry amount
+ // (or just add the carry amount if this is a new byte -- undefined case)
+ if (currentByte === undefined) {
+ currentByte = carry
+ } else {
+ currentByte = currentByte * 58 + carry
+ }
+
+ // find the new carry amount (1-byte shift of current byte value)
+ carry = currentByte >> 8;
+ // reset the current byte to the remainder (the carry amount will pass on the overflow)
+ decodedBytes[j] = currentByte % 256;
+ j++
+ }
+ }
+
+ var result = leadingZeros.concat(decodedBytes.reverse());
+ return new Uint8Array(result);
+ }
+
+ /**
+ * Returns a private key from private key bytes, represented in
+ * the encoding and format specified by `keyFormat`. Defaults to
+ * hex-encoding if `keyFormat` isn't passed.
* @param {Uint8Array} privateKeyBytes
+ * @param {string} keyFormat Can be "HEXADECIMAL" or "SOLANA"
*/
- const parseKey = privateKeyBytes => {
- const privateKeyHexString = uint8arrayToHexString(privateKeyBytes);
- return "0x" + privateKeyHexString;
+ const encodeKey = async (privateKeyBytes, keyFormat, publicKeyBytes) => {
+ switch (keyFormat) {
+ case "SOLANA":
+ if (!publicKeyBytes) {
+ throw new Error("public key must be specified for SOLANA key format");
+ }
+ if (privateKeyBytes.length !== 32) {
+ throw new Error(`invalid private key length. Expected 32 bytes. Got ${privateKeyBytes.length}.`);
+ }
+ if (publicKeyBytes.length !== 32) {
+ throw new Error(`invalid public key length. Expected 32 bytes. Got ${publicKeyBytes.length}.`);
+ }
+ const concatenatedBytes = new Uint8Array(64);
+ concatenatedBytes.set(privateKeyBytes, 0);
+ concatenatedBytes.set(publicKeyBytes, 32);
+ return base58Encode(concatenatedBytes);
+ case "HEXADECIMAL":
+ return "0x" + uint8arrayToHexString(privateKeyBytes);
+ default:
+ console.warn(`invalid key format: ${keyFormat}. Defaulting to HEXADECIMAL.`);
+ return "0x" + uint8arrayToHexString(privateKeyBytes);
+ }
}
/**
@@ -281,7 +401,7 @@ Message log
* from wallet bytes.
* @param {Uint8Array} walletBytes
*/
- const parseWallet = walletBytes => {
+ const encodeWallet = walletBytes => {
const decoder = new TextDecoder("utf-8");
const wallet = decoder.decode(walletBytes);
let mnemonic;
@@ -310,8 +430,10 @@ Message log
setEmbeddedKey,
onResetEmbeddedKey,
p256JWKPrivateToPublic,
- parseKey,
- parseWallet,
+ base58Encode,
+ base58Decode,
+ encodeKey,
+ encodeWallet,
sendMessageUp,
logMessage,
uint8arrayFromHexString,
@@ -329,6 +451,7 @@ Message log