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