From 84aa08235b58e246a6d38917bef277910105369e Mon Sep 17 00:00:00 2001 From: Olivia Thet Date: Tue, 12 Mar 2024 00:55:12 -0400 Subject: [PATCH 1/9] utf8 encode the exported private key --- export/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/export/index.html b/export/index.html index 23da3d6..2605836 100644 --- a/export/index.html +++ b/export/index.html @@ -272,8 +272,8 @@

Message log

* @param {Uint8Array} privateKeyBytes */ const parseKey = privateKeyBytes => { - const privateKeyHexString = uint8arrayToHexString(privateKeyBytes); - return "0x" + privateKeyHexString; + const decoder = new TextDecoder("utf-8"); + return decoder.decode(privateKeyBytes); } /** From 09846aa8a6d019ea4e2d6d9d24742f13b80b0bd5 Mon Sep 17 00:00:00 2001 From: Olivia Thet Date: Tue, 12 Mar 2024 01:26:25 -0400 Subject: [PATCH 2/9] rename to extract encrypted bundle. update tests --- export/index.html | 2 +- export/index.test.js | 16 +++++++++++++--- import/index.html | 25 +++++++++++++++++-------- import/standalone.html | 19 ++++++++++++++----- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/export/index.html b/export/index.html index 2605836..b440819 100644 --- a/export/index.html +++ b/export/index.html @@ -268,7 +268,7 @@

Message log

} /** - * Returns a hex-encoded raw private key from private key bytes. + * Returns a hex-encoded or base58-encoded private key from private key bytes. * @param {Uint8Array} privateKeyBytes */ const parseKey = privateKeyBytes => { diff --git a/export/index.test.js b/export/index.test.js index f60969e..ab7d7e0 100644 --- a/export/index.test.js +++ b/export/index.test.js @@ -84,12 +84,22 @@ describe("TKHQ", () => { expect(key.key_ops).toContain("deriveBits"); }) - it("parses private key correctly", async () => { - const keyHex = "0x13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e"; - const parsedKey = TKHQ.parseKey(TKHQ.uint8arrayFromHexString(keyHex.slice(2))); + it("parses hex-encoded private key correctly", async () => { + const keyHex = "13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e"; + const encoder = new TextEncoder("utf-8"); + const encodedKey = encoder.encode(keyHex); + const parsedKey = TKHQ.parseKey(encodedKey); expect(parsedKey).toEqual(keyHex); }) + it("parses base58-encoded private key correctly", async () => { + const keybase58 = "5HueCGU8rMjxExZhSwp1xXQPBDsMaZwk74rZkDfDXvDVpi7L6vBZp2uhZLyStgM9xXdwvCLSrqQfJCVDqWsRU8T7"; + const encoder = new TextEncoder("utf-8"); + const encodedKey = encoder.encode(keybase58); + const parsedKey = TKHQ.parseKey(encodedKey); + expect(parsedKey).toEqual(keybase58); + }) + it("parses wallet with only mnemonic correctly", async () => { const mnemonic = "suffer surround soup duck goose patrol add unveil appear eye neglect hurry alpha project tomorrow embody hen wish twenty join notable amused burden treat"; const encoder = new TextEncoder("utf-8"); diff --git a/import/index.html b/import/index.html index 95dbbe4..7928ada 100644 --- a/import/index.html +++ b/import/index.html @@ -12,7 +12,7 @@ @@ -93,6 +90,84 @@ .join(''); } + /** + * 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 private key bytes from a private key, represented in + * the encoding and format specified by `keyFormat`. Defaults to + * hex-encoding if `keyFormat` isn't passed. + * @param {string} privateKey + * @param {string} keyFormat Can be "HEXADECIMAL" or "SOLANA" + */ + const formatKey = (privateKey, keyFormat) => { + switch (keyFormat) { + case "SOLANA": + const decodedKeyBytes = base58Decode(privateKey); + if (decodedKeyBytes.length !== 64) { + throw new Error(`invalid key length. Expected 64 bytes. Got ${decodedKeyBytes.length()}.`); + } + return decodedKeyBytes.subarray(0, 32); + case "HEXADECIMAL": + if (privateKey.startsWith("0x")) { + return uint8arrayFromHexString(privateKey.slice(2)); + } + return uint8arrayFromHexString(privateKey); + default: + console.warn(`invalid key format: ${keyFormat}. Defaulting to HEXADECIMAL.`); + if (privateKey.startsWith("0x")) { + return uint8arrayFromHexString(privateKey.slice(2)); + } + return uint8arrayFromHexString(privateKey); + } + } + /** * Additional Associated Data (AAD) in the format dictated by the enclave_encrypt crate. */ @@ -123,6 +198,8 @@ sendMessageUp, uint8arrayFromHexString, uint8arrayToHexString, + base58Decode, + formatKey, additionalAssociatedData } }(); @@ -157,14 +234,14 @@ // TODO: deprecate EXTRACT_WALLET_ENCRYPTED_BUNDLE in favor of EXTRACT_ENCRYPTED_BUNDLE if (event.data && event.data["type"] == "EXTRACT_WALLET_ENCRYPTED_BUNDLE") { try { - await onExtractEncryptedBundle() + await onExtractWalletEncryptedBundle() } catch (e) { TKHQ.sendMessageUp("ERROR", e.toString()); } } - if (event.data && event.data["type"] == "EXTRACT_ENCRYPTED_BUNDLE") { + if (event.data && event.data["type"] == "EXTRACT_KEY_ENCRYPTED_BUNDLE") { try { - await onExtractEncryptedBundle() + await onExtractKeyEncryptedBundle(event.data["keyFormat"]) } catch (e) { TKHQ.sendMessageUp("ERROR", e.toString()); } @@ -202,8 +279,7 @@ } /** - * Function triggered when EXTRACT_ENCRYPTED_BUNDLE (and previously EXTRACT_WALLET_ENCRYPTED_BUNDLE) - * event is received. + * Function triggered when EXTRACT_WALLET_ENCRYPTED_BUNDLE event is received. * Prerequisite: This function uses the target public key in local storage that is imported * from the INJECT_IMPORT_BUNDLE event. * Uses the target public key in local storage to encrypt the text entered in the @@ -211,15 +287,18 @@ * an `encrypted_bundle` containing the ciphertext and encapped public key. * Example bundle: {"encappedPublic":"0497f33f3306f67f4402d4824e15b63b04786b6558d417aac2fef69051e46fa7bfbe776b142e4ded4f02097617a7588e93c53b71f900a4a8831a31be6f95e5f60f","ciphertext":"c17c3085505f3c094f0fa61791395b83ab1d8c90bdf9f12a64fc6e2e9cba266beb528f65c88bd933e36e6203752a9b63e6a92290a0ab6bf0ed591cf7bfa08006001e2cc63870165dc99ec61554ffdc14dea7d567e62cceed29314ae6c71a013843f5c06146dee5bf9c1d"} */ - const onExtractEncryptedBundle = async () => { + const onExtractWalletEncryptedBundle = async () => { // Get target embedded key from previous step (onInjectImportBundle) const targetPublicKeyJwk = TKHQ.getTargetEmbeddedKey(); if (targetPublicKeyJwk == null) { throw new Error("no target key found"); } - // Get plaintext wallet seedphrase or private key - const plaintext = document.getElementById("plaintext").value; + // Get plaintext wallet mnemonic + const plaintext = document.getElementById("plaintext").value.trim(); + if (!plaintext) { + throw new Error("no wallet mnemonic entered"); + } const plaintextBuf = new TextEncoder().encode(plaintext); // Encrypt the bundle using the enclave target public key @@ -233,6 +312,40 @@ TKHQ.sendMessageUp("ENCRYPTED_BUNDLE_EXTRACTED", encryptedBundle) } + /** + * Function triggered when EXTRACT_KEY_ENCRYPTED_BUNDLE event is received. + * Prerequisite: This function uses the target public key in local storage that is imported + * from the INJECT_IMPORT_BUNDLE event. + * Uses the target public key in local storage to encrypt the text entered in the + * `plaintext` textarea element. Upon successful encryption, sends + * an `encrypted_bundle` containing the ciphertext and encapped public key. + * Example bundle: {"encappedPublic":"0497f33f3306f67f4402d4824e15b63b04786b6558d417aac2fef69051e46fa7bfbe776b142e4ded4f02097617a7588e93c53b71f900a4a8831a31be6f95e5f60f","ciphertext":"c17c3085505f3c094f0fa61791395b83ab1d8c90bdf9f12a64fc6e2e9cba266beb528f65c88bd933e36e6203752a9b63e6a92290a0ab6bf0ed591cf7bfa08006001e2cc63870165dc99ec61554ffdc14dea7d567e62cceed29314ae6c71a013843f5c06146dee5bf9c1d"} + */ + const onExtractKeyEncryptedBundle = async keyFormat => { + // Get target embedded key from previous step (onInjectImportBundle) + const targetPublicKeyJwk = TKHQ.getTargetEmbeddedKey(); + if (targetPublicKeyJwk == null) { + throw new Error("no target key found"); + } + + // Get plaintext private key + const plaintext = document.getElementById("plaintext").value.trim(); + if (!plaintext) { + throw new Error("no private key entered"); + } + const plaintextBuf = TKHQ.formatKey(plaintext, keyFormat); + + // Encrypt the bundle using the enclave target public key + const encryptedBundle = await HpkeEncrypt( + { + plaintextBuf, + receiverPubJwk: targetPublicKeyJwk, + }); + + // Send up ENCRYPTED_BUNDLE_EXTRACTED message + TKHQ.sendMessageUp("ENCRYPTED_BUNDLE_EXTRACTED", encryptedBundle) + } + const HpkeEncrypt = async ({ plaintextBuf, receiverPubJwk }) => { const kemContext = new hpke.DhkemP256HkdfSha256(); const receiverPub = await kemContext.importKey("jwk", {...receiverPubJwk}, true); diff --git a/import/index.test.js b/import/index.test.js index 65871c2..fb9185d 100644 --- a/import/index.test.js +++ b/import/index.test.js @@ -51,6 +51,38 @@ describe("TKHQ", () => { expect(key.key_ops).toEqual([]); }) + it("formats hex-encoded private key correctly by default", async () => { + const keyHex = "0x13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e"; + const keyBytes = TKHQ.uint8arrayFromHexString(keyHex.slice(2)); + const formattedKey = TKHQ.formatKey(keyHex); + expect(formattedKey.length).toEqual(keyBytes.length); + for (let i = 0; i < formattedKey.length; i++) { + expect(formattedKey[i]).toEqual(keyBytes[i]); + } + }) + + it("parses hex-encoded private key correctly", async () => { + const keyHex = "0x13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e"; + const keyBytes = TKHQ.uint8arrayFromHexString(keyHex.slice(2)); + const formattedKey = TKHQ.formatKey(keyHex, "HEXADECIMAL"); + expect(formattedKey.length).toEqual(keyBytes.length); + for (let i = 0; i < formattedKey.length; i++) { + expect(formattedKey[i]).toEqual(keyBytes[i]); + } + }) + + it("parses solana private key correctly", async () => { + const keySol = "2P3qgS5A18gGmZJmYHNxYrDYPyfm6S3dJgs8tPW6ki6i2o4yx7K8r5N8CF7JpEtQiW8mx1kSktpgyDG1xuWNzfsM"; + const keyBytes = TKHQ.base58Decode(keySol); + expect(keyBytes.length).toEqual(64); + const keyPrivBytes = keyBytes.subarray(0, 32); + const formattedKey = TKHQ.formatKey(keySol, "SOLANA"); + expect(formattedKey.length).toEqual(keyPrivBytes.length); + for (let i = 0; i < formattedKey.length; i++) { + expect(formattedKey[i]).toEqual(keyPrivBytes[i]); + } + }) + it("contains additionalAssociatedData", async () => { // This is a trivial helper; concatenates the 2 arrays! expect(TKHQ.additionalAssociatedData(new Uint8Array([1, 2]), new Uint8Array([3, 4])).buffer).toEqual(new Uint8Array([1, 2, 3, 4]).buffer); diff --git a/import/standalone.html b/import/standalone.html index bf3b1d8..a4ff2a7 100644 --- a/import/standalone.html +++ b/import/standalone.html @@ -91,8 +91,6 @@

Message log

window.TKHQ = function() { /** constants for LocalStorage */ const TURNKEY_TARGET_EMBEDDED_KEY = "TURNKEY_TARGET_EMBEDDED_KEY" - /** 1 week in milliseconds */ - const TURNKEY_TARGET_EMBEDDED_KEY_TTL_IN_MILLIS = 1000 * 60 * 60 * 24 * 7; /* * Import a key to encrypt to as a CryptoKey and export it as a JSON Web Key. @@ -140,6 +138,84 @@

Message log

.join(''); } + /** + * 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 private key bytes from a private key, represented in + * the encoding and format specified by `keyFormat`. Defaults to + * hex-encoding if `keyFormat` isn't passed. + * @param {string} privateKey + * @param {string} keyFormat Can be "HEXADECIMAL" or "SOLANA" + */ + const formatKey = (privateKey, keyFormat) => { + switch (keyFormat) { + case "SOLANA": + const decodedKeyBytes = base58Decode(privateKey); + if (decodedKeyBytes.length !== 64) { + throw new Error(`invalid key length. Expected 64 bytes. Got ${decodedKeyBytes.length()}.`); + } + return decodedKeyBytes.subarray(0, 32); + case "HEXADECIMAL": + if (privateKey.startsWith("0x")) { + return uint8arrayFromHexString(privateKey.slice(2)); + } + return uint8arrayFromHexString(privateKey); + default: + console.warn(`invalid key format: ${keyFormat}. Defaulting to HEXADECIMAL.`); + if (privateKey.startsWith("0x")) { + return uint8arrayFromHexString(privateKey.slice(2)); + } + return uint8arrayFromHexString(privateKey); + } + } + /** * Additional Associated Data (AAD) in the format dictated by the enclave_encrypt crate. */ @@ -182,6 +258,8 @@

Message log

logMessage, uint8arrayFromHexString, uint8arrayToHexString, + base58Decode, + formatKey, additionalAssociatedData } }(); @@ -218,15 +296,15 @@

Message log

if (event.data && event.data["type"] == "EXTRACT_WALLET_ENCRYPTED_BUNDLE") { TKHQ.logMessage(`⬇️ Received message ${event.data["type"]}: ${event.data["value"]}`); try { - await onExtractEncryptedBundle(event.data["value"]) + await onExtractWalletEncryptedBundle(event.data["value"]) } catch (e) { TKHQ.sendMessageUp("ERROR", e.toString()); } } - if (event.data && event.data["type"] == "EXTRACT_ENCRYPTED_BUNDLE") { - TKHQ.logMessage(`⬇️ Received message ${event.data["type"]}: ${event.data["value"]}`); + if (event.data && event.data["type"] == "EXTRACT_KEY_ENCRYPTED_BUNDLE") { + TKHQ.logMessage(`⬇️ Received message ${event.data["type"]}: ${event.data["value"]}, ${event.data["keyFormat"]}`); try { - await onExtractEncryptedBundle(event.data["value"]) + await onExtractKeyEncryptedBundle(event.data["value"], event.data["keyFormat"]) } catch (e) { TKHQ.sendMessageUp("ERROR", e.toString()); } @@ -252,6 +330,15 @@

Message log

"value": document.getElementById("plaintext").value, }) }, false); + document.getElementById("encrypt-key-bundle").addEventListener("click", async e => { + e.preventDefault(); + window.postMessage({ + "type": "EXTRACT_KEY_ENCRYPTED_BUNDLE", + "value": document.getElementById("plaintext").value, + "keyFormat": document.getElementById("keyFormat").value, + "publicKey": document.getElementById("publicKey").value, + }) + }, false); }, false); /** @@ -292,15 +379,55 @@

Message log

* an `encrypted_bundle` containing the ciphertext and encapped public key. * Example bundle: {"encappedPublic":"0497f33f3306f67f4402d4824e15b63b04786b6558d417aac2fef69051e46fa7bfbe776b142e4ded4f02097617a7588e93c53b71f900a4a8831a31be6f95e5f60f","ciphertext":"c17c3085505f3c094f0fa61791395b83ab1d8c90bdf9f12a64fc6e2e9cba266beb528f65c88bd933e36e6203752a9b63e6a92290a0ab6bf0ed591cf7bfa08006001e2cc63870165dc99ec61554ffdc14dea7d567e62cceed29314ae6c71a013843f5c06146dee5bf9c1d"} */ - const onExtractEncryptedBundle = async bundle => { + const onExtractWalletEncryptedBundle = async bundle => { // Get target embedded key from previous step (onInjectImportBundle) const targetPublicKeyJwk = TKHQ.getTargetEmbeddedKey(); if (targetPublicKeyJwk == null) { throw new Error("no target key found"); } + // Get plaintext wallet mnemonic + const plaintext = bundle.trim(); + if (!plaintext) { + throw new Error("no wallet mnemonic entered"); + } + const plaintextBuf = new TextEncoder().encode(plaintext); + + // Encrypt the bundle using the enclave target public key + const encryptedBundle = await HpkeEncrypt( + { + plaintextBuf, + receiverPubJwk: targetPublicKeyJwk, + }); + + // Send up ENCRYPTED_BUNDLE_EXTRACTED message + TKHQ.sendMessageUp("ENCRYPTED_BUNDLE_EXTRACTED", encryptedBundle) + } + + /** + * Function triggered when EXTRACT_KEY_ENCRYPTED_BUNDLE event is received. + * Prerequisite: This function uses the target public key in local storage that is imported + * from the INJECT_IMPORT_BUNDLE event. + * Uses the target public key in local storage to encrypt the text entered in the + * `plaintext` textarea element. Upon successful encryption, sends + * an `encrypted_bundle` containing the ciphertext and encapped public key. + * Example bundle: {"encappedPublic":"0497f33f3306f67f4402d4824e15b63b04786b6558d417aac2fef69051e46fa7bfbe776b142e4ded4f02097617a7588e93c53b71f900a4a8831a31be6f95e5f60f","ciphertext":"c17c3085505f3c094f0fa61791395b83ab1d8c90bdf9f12a64fc6e2e9cba266beb528f65c88bd933e36e6203752a9b63e6a92290a0ab6bf0ed591cf7bfa08006001e2cc63870165dc99ec61554ffdc14dea7d567e62cceed29314ae6c71a013843f5c06146dee5bf9c1d"} + */ + const onExtractKeyEncryptedBundle = async (bundle, keyFormat) => { + // Get target embedded key from previous step (onInjectImportBundle) + const targetPublicKeyJwk = TKHQ.getTargetEmbeddedKey(); + if (targetPublicKeyJwk == null) { + throw new Error("no target key found"); + } + + // Get plaintext private key + const plaintext = bundle.trim(); + if (!plaintext) { + throw new Error("no private key entered"); + } + const plaintextBuf = TKHQ.formatKey(plaintext, keyFormat); + // Encrypt the bundle using the enclave target public key - const plaintextBuf = new TextEncoder().encode(bundle); const encryptedBundle = await HpkeEncrypt( { plaintextBuf, From c7d0d1e9ea77c5d8fa859aa444cd90dcfcc1d5b4 Mon Sep 17 00:00:00 2001 From: Olivia Thet Date: Wed, 13 Mar 2024 18:39:10 -0400 Subject: [PATCH 4/9] add dropdown components to import standalone mode --- import/standalone.html | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/import/standalone.html b/import/standalone.html index a4ff2a7..7a49caf 100644 --- a/import/standalone.html +++ b/import/standalone.html @@ -17,11 +17,14 @@ max-width: 1024px; margin: auto; } + form { + text-align: left; + } label { display:inline-block; width: 8em; } - input[type=text] { + input[type=text], select { width: 40em; margin: 0.5em; font-family: 'Courier New', Courier, monospace; @@ -34,7 +37,7 @@ input:disabled { background-color: rgb(239, 243, 240); } - #inject-import-bundle, #encrypt-wallet-bundle { + #inject-import-bundle, #encrypt-wallet-bundle, #encrypt-key-bundle { color: white; width: 10em; font-size: 1em; @@ -64,20 +67,32 @@ -

Import Key Material

-

Bundles

+

Initialize Import


+

Import Wallet

- +

+

Import Private Key

+
+ + + + + +
+

Message log

Below we display a log of the messages sent / received. The forms above send messages, and the code communicates results by sending events via the postMessage API.

@@ -327,14 +342,14 @@

Message log

e.preventDefault(); window.postMessage({ "type": "EXTRACT_WALLET_ENCRYPTED_BUNDLE", - "value": document.getElementById("plaintext").value, + "value": document.getElementById("wallet-plaintext").value, }) }, false); document.getElementById("encrypt-key-bundle").addEventListener("click", async e => { e.preventDefault(); window.postMessage({ "type": "EXTRACT_KEY_ENCRYPTED_BUNDLE", - "value": document.getElementById("plaintext").value, + "value": document.getElementById("key-plaintext").value, "keyFormat": document.getElementById("keyFormat").value, "publicKey": document.getElementById("publicKey").value, }) From c02771be9c48dfcde1426318006f980b7c006e0f Mon Sep 17 00:00:00 2001 From: Olivia Thet Date: Wed, 13 Mar 2024 21:16:00 -0400 Subject: [PATCH 5/9] rename parseKey to encodeKey and formatKey to decodeKey --- export/index.html | 12 ++++++------ export/index.test.js | 38 +++++++++++++++++--------------------- import/index.html | 6 +++--- import/index.test.js | 30 +++++++++++++++--------------- import/standalone.html | 6 +++--- 5 files changed, 44 insertions(+), 48 deletions(-) diff --git a/export/index.html b/export/index.html index 3f30b65..f64cd80 100644 --- a/export/index.html +++ b/export/index.html @@ -373,7 +373,7 @@

Message log

* @param {string} keyFormat Can be "HEXADECIMAL" or "SOLANA" * @param {string} publicKey Hex-encoded public key, needed for Solana private keys */ - const parseKey = (privateKeyBytes, keyFormat, publicKey) => { + const encodeKey = (privateKeyBytes, keyFormat, publicKey) => { switch (keyFormat) { case "SOLANA": if (!publicKey) { @@ -403,7 +403,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; @@ -434,8 +434,8 @@

Message log

p256JWKPrivateToPublic, base58Encode, base58Decode, - parseKey, - parseWallet, + encodeKey, + encodeWallet, sendMessageUp, logMessage, uint8arrayFromHexString, @@ -578,7 +578,7 @@

Message log

const keyBytes = await decryptBundle(bundle); // Parse the decrypted key bytes - const key = TKHQ.parseKey(new Uint8Array(keyBytes), keyFormat, publicKey); + const key = TKHQ.encodeKey(new Uint8Array(keyBytes), keyFormat, publicKey); // Display only the key displayKey(key); @@ -596,7 +596,7 @@

Message log

const walletBytes = await decryptBundle(bundle); // Parse the decrypted wallet bytes - const wallet = TKHQ.parseWallet(new Uint8Array(walletBytes)); + const wallet = TKHQ.encodeWallet(new Uint8Array(walletBytes)); // Display only the wallet's mnemonic displayKey(wallet.mnemonic); diff --git a/export/index.test.js b/export/index.test.js index f1e2b84..babac54 100644 --- a/export/index.test.js +++ b/export/index.test.js @@ -84,19 +84,19 @@ describe("TKHQ", () => { expect(key.key_ops).toContain("deriveBits"); }) - it("parses hex-encoded private key correctly by default", async () => { + it("encodes hex-encoded private key correctly by default", async () => { const keyHex = "0x13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e"; - const parsedKey = TKHQ.parseKey(TKHQ.uint8arrayFromHexString(keyHex.slice(2))); - expect(parsedKey).toEqual(keyHex); + const encodedKey = TKHQ.encodeKey(TKHQ.uint8arrayFromHexString(keyHex.slice(2))); + expect(encodedKey).toEqual(keyHex); }) - it("parses hex-encoded private key correctly", async () => { + it("encodes hex-encoded private key correctly", async () => { const keyHex = "0x13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e"; - const parsedKey = TKHQ.parseKey(TKHQ.uint8arrayFromHexString(keyHex.slice(2)), "HEXADECIMAL"); - expect(parsedKey).toEqual(keyHex); + const encodedKey = TKHQ.encodeKey(TKHQ.uint8arrayFromHexString(keyHex.slice(2)), "HEXADECIMAL"); + expect(encodedKey).toEqual(keyHex); }) - it("parses solana private key correctly", async () => { + it("encodes solana private key correctly", async () => { const keySol = "2P3qgS5A18gGmZJmYHNxYrDYPyfm6S3dJgs8tPW6ki6i2o4yx7K8r5N8CF7JpEtQiW8mx1kSktpgyDG1xuWNzfsM"; const keySolBytes = TKHQ.base58Decode(keySol); expect(keySolBytes.length).toEqual(64); @@ -104,27 +104,23 @@ describe("TKHQ", () => { const keyPubBytes = keySolBytes.subarray(32, 64); const keyPubHex = TKHQ.uint8arrayToHexString(keyPubBytes); - const parsedKey = TKHQ.parseKey(keyPrivBytes, "SOLANA", keyPubHex); - expect(parsedKey).toEqual(keySol); + const encodedKey = TKHQ.encodeKey(keyPrivBytes, "SOLANA", keyPubHex); + expect(encodedKey).toEqual(keySol); }) - it("parses wallet with only mnemonic correctly", async () => { + it("encodes wallet with only mnemonic correctly", async () => { const mnemonic = "suffer surround soup duck goose patrol add unveil appear eye neglect hurry alpha project tomorrow embody hen wish twenty join notable amused burden treat"; - const encoder = new TextEncoder("utf-8"); - const encodedWallet = encoder.encode(mnemonic); - const parsedWallet = TKHQ.parseWallet(encodedWallet); - expect(parsedWallet.mnemonic).toEqual(mnemonic); - expect(parsedWallet.passphrase).toBeNull(); + const encodedWallet = TKHQ.encodeWallet(new TextEncoder("utf-8").encode(mnemonic)); + expect(encodedWallet.mnemonic).toEqual(mnemonic); + expect(encodedWallet.passphrase).toBeNull(); }) - it("parses wallet mnemonic and passphrase correctly", async () => { + it("encodes wallet mnemonic and passphrase correctly", async () => { const mnemonic = "suffer surround soup duck goose patrol add unveil appear eye neglect hurry alpha project tomorrow embody hen wish twenty join notable amused burden treat"; const passphrase = "secret!"; - const encoder = new TextEncoder("utf-8"); - const encodedWallet = encoder.encode(mnemonic + "\n" + passphrase); - const parsedWallet = TKHQ.parseWallet(encodedWallet); - expect(parsedWallet.mnemonic).toEqual(mnemonic); - expect(parsedWallet.passphrase).toEqual(passphrase); + const encodedWallet = TKHQ.encodeWallet(new TextEncoder("utf-8").encode(mnemonic + "\n" + passphrase)); + expect(encodedWallet.mnemonic).toEqual(mnemonic); + expect(encodedWallet.passphrase).toEqual(passphrase); }) it("contains p256JWKPrivateToPublic", async () => { diff --git a/import/index.html b/import/index.html index 5b38148..ab0763f 100644 --- a/import/index.html +++ b/import/index.html @@ -146,7 +146,7 @@ * @param {string} privateKey * @param {string} keyFormat Can be "HEXADECIMAL" or "SOLANA" */ - const formatKey = (privateKey, keyFormat) => { + const decodeKey = (privateKey, keyFormat) => { switch (keyFormat) { case "SOLANA": const decodedKeyBytes = base58Decode(privateKey); @@ -199,7 +199,7 @@ uint8arrayFromHexString, uint8arrayToHexString, base58Decode, - formatKey, + decodeKey, additionalAssociatedData } }(); @@ -333,7 +333,7 @@ if (!plaintext) { throw new Error("no private key entered"); } - const plaintextBuf = TKHQ.formatKey(plaintext, keyFormat); + const plaintextBuf = TKHQ.decodeKey(plaintext, keyFormat); // Encrypt the bundle using the enclave target public key const encryptedBundle = await HpkeEncrypt( diff --git a/import/index.test.js b/import/index.test.js index fb9185d..9629499 100644 --- a/import/index.test.js +++ b/import/index.test.js @@ -51,35 +51,35 @@ describe("TKHQ", () => { expect(key.key_ops).toEqual([]); }) - it("formats hex-encoded private key correctly by default", async () => { + it("decodes hex-encoded private key correctly by default", async () => { const keyHex = "0x13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e"; const keyBytes = TKHQ.uint8arrayFromHexString(keyHex.slice(2)); - const formattedKey = TKHQ.formatKey(keyHex); - expect(formattedKey.length).toEqual(keyBytes.length); - for (let i = 0; i < formattedKey.length; i++) { - expect(formattedKey[i]).toEqual(keyBytes[i]); + const decodedKey = TKHQ.decodeKey(keyHex); + expect(decodedKey.length).toEqual(keyBytes.length); + for (let i = 0; i < decodedKey.length; i++) { + expect(decodedKey[i]).toEqual(keyBytes[i]); } }) - it("parses hex-encoded private key correctly", async () => { + it("decodes hex-encoded private key correctly", async () => { const keyHex = "0x13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e"; const keyBytes = TKHQ.uint8arrayFromHexString(keyHex.slice(2)); - const formattedKey = TKHQ.formatKey(keyHex, "HEXADECIMAL"); - expect(formattedKey.length).toEqual(keyBytes.length); - for (let i = 0; i < formattedKey.length; i++) { - expect(formattedKey[i]).toEqual(keyBytes[i]); + const decodedKey = TKHQ.decodeKey(keyHex, "HEXADECIMAL"); + expect(decodedKey.length).toEqual(keyBytes.length); + for (let i = 0; i < decodedKey.length; i++) { + expect(decodedKey[i]).toEqual(keyBytes[i]); } }) - it("parses solana private key correctly", async () => { + it("decodes solana private key correctly", async () => { const keySol = "2P3qgS5A18gGmZJmYHNxYrDYPyfm6S3dJgs8tPW6ki6i2o4yx7K8r5N8CF7JpEtQiW8mx1kSktpgyDG1xuWNzfsM"; const keyBytes = TKHQ.base58Decode(keySol); expect(keyBytes.length).toEqual(64); const keyPrivBytes = keyBytes.subarray(0, 32); - const formattedKey = TKHQ.formatKey(keySol, "SOLANA"); - expect(formattedKey.length).toEqual(keyPrivBytes.length); - for (let i = 0; i < formattedKey.length; i++) { - expect(formattedKey[i]).toEqual(keyPrivBytes[i]); + const decodedKey = TKHQ.decodeKey(keySol, "SOLANA"); + expect(decodedKey.length).toEqual(keyPrivBytes.length); + for (let i = 0; i < decodedKey.length; i++) { + expect(decodedKey[i]).toEqual(keyPrivBytes[i]); } }) diff --git a/import/standalone.html b/import/standalone.html index 7a49caf..87b6daf 100644 --- a/import/standalone.html +++ b/import/standalone.html @@ -209,7 +209,7 @@

Message log

* @param {string} privateKey * @param {string} keyFormat Can be "HEXADECIMAL" or "SOLANA" */ - const formatKey = (privateKey, keyFormat) => { + const decodeKey = (privateKey, keyFormat) => { switch (keyFormat) { case "SOLANA": const decodedKeyBytes = base58Decode(privateKey); @@ -274,7 +274,7 @@

Message log

uint8arrayFromHexString, uint8arrayToHexString, base58Decode, - formatKey, + decodeKey, additionalAssociatedData } }(); @@ -440,7 +440,7 @@

Message log

if (!plaintext) { throw new Error("no private key entered"); } - const plaintextBuf = TKHQ.formatKey(plaintext, keyFormat); + const plaintextBuf = TKHQ.decodeKey(plaintext, keyFormat); // Encrypt the bundle using the enclave target public key const encryptedBundle = await HpkeEncrypt( From db6b22cd0f66ea4ae8c8166187b055a99aae963a Mon Sep 17 00:00:00 2001 From: Olivia Thet Date: Thu, 14 Mar 2024 12:13:32 -0400 Subject: [PATCH 6/9] fix html ids --- export/index.html | 2 +- import/standalone.html | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/export/index.html b/export/index.html index f64cd80..1eca09f 100644 --- a/export/index.html +++ b/export/index.html @@ -92,7 +92,7 @@

Inject Key Export Bundle


- diff --git a/import/standalone.html b/import/standalone.html index 87b6daf..cbef9f9 100644 --- a/import/standalone.html +++ b/import/standalone.html @@ -76,18 +76,18 @@

Initialize Import


Import Wallet

- +

Import Private Key

- + - @@ -350,8 +350,7 @@

Message log

window.postMessage({ "type": "EXTRACT_KEY_ENCRYPTED_BUNDLE", "value": document.getElementById("key-plaintext").value, - "keyFormat": document.getElementById("keyFormat").value, - "publicKey": document.getElementById("publicKey").value, + "keyFormat": document.getElementById("key-import-format").value, }) }, false); }, false); From 21649061e9069edfb22393e2ab46adeef873403e Mon Sep 17 00:00:00 2001 From: Olivia Thet Date: Fri, 15 Mar 2024 00:47:43 -0400 Subject: [PATCH 7/9] wrong var --- export/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/export/index.html b/export/index.html index 1eca09f..4acb486 100644 --- a/export/index.html +++ b/export/index.html @@ -380,11 +380,11 @@

Message log

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()}.`); + throw new Error(`invalid private key length. Expected 32 bytes. Got ${privateKeyBytes.length}.`); } const publicKeyBytes = uint8arrayFromHexString(publicKey); if (publicKeyBytes.length !== 32) { - throw new Error(`invalid public key length. Expected 32 bytes. Got ${privateKeyBytes.length()}.`); + throw new Error(`invalid public key length. Expected 32 bytes. Got ${publicKeyBytes.length}.`); } const concatenatedBytes = new Uint8Array(64); concatenatedBytes.set(privateKeyBytes, 0); From 26e13e1963c96e3d47484572613e8e2d6e392269 Mon Sep 17 00:00:00 2001 From: Olivia Thet Date: Fri, 15 Mar 2024 00:42:16 -0400 Subject: [PATCH 8/9] es module noble/ed25519 --- export/index.html | 23 ++++++++++++++++------- export/index.test.js | 8 +++----- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/export/index.html b/export/index.html index 4acb486..b774730 100644 --- a/export/index.html +++ b/export/index.html @@ -371,18 +371,16 @@

Message log

* hex-encoding if `keyFormat` isn't passed. * @param {Uint8Array} privateKeyBytes * @param {string} keyFormat Can be "HEXADECIMAL" or "SOLANA" - * @param {string} publicKey Hex-encoded public key, needed for Solana private keys */ - const encodeKey = (privateKeyBytes, keyFormat, publicKey) => { + const encodeKey = async (privateKeyBytes, keyFormat, publicKeyBytes) => { switch (keyFormat) { case "SOLANA": - if (!publicKey) { + 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}.`); } - const publicKeyBytes = uint8arrayFromHexString(publicKey); if (publicKeyBytes.length !== 32) { throw new Error(`invalid public key length. Expected 32 bytes. Got ${publicKeyBytes.length}.`); } @@ -453,6 +451,7 @@

Message log

- diff --git a/export/index.test.js b/export/index.test.js index babac54..994e387 100644 --- a/export/index.test.js +++ b/export/index.test.js @@ -86,13 +86,13 @@ describe("TKHQ", () => { it("encodes hex-encoded private key correctly by default", async () => { const keyHex = "0x13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e"; - const encodedKey = TKHQ.encodeKey(TKHQ.uint8arrayFromHexString(keyHex.slice(2))); + const encodedKey = await TKHQ.encodeKey(TKHQ.uint8arrayFromHexString(keyHex.slice(2))); expect(encodedKey).toEqual(keyHex); }) it("encodes hex-encoded private key correctly", async () => { const keyHex = "0x13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e"; - const encodedKey = TKHQ.encodeKey(TKHQ.uint8arrayFromHexString(keyHex.slice(2)), "HEXADECIMAL"); + const encodedKey = await TKHQ.encodeKey(TKHQ.uint8arrayFromHexString(keyHex.slice(2)), "HEXADECIMAL"); expect(encodedKey).toEqual(keyHex); }) @@ -102,9 +102,7 @@ describe("TKHQ", () => { expect(keySolBytes.length).toEqual(64); const keyPrivBytes = keySolBytes.subarray(0, 32); const keyPubBytes = keySolBytes.subarray(32, 64); - const keyPubHex = TKHQ.uint8arrayToHexString(keyPubBytes); - - const encodedKey = TKHQ.encodeKey(keyPrivBytes, "SOLANA", keyPubHex); + const encodedKey = await TKHQ.encodeKey(keyPrivBytes, "SOLANA", keyPubBytes); expect(encodedKey).toEqual(keySol); }) From 5e4af1f159a35d89be2ec6ca7afcc614db8f2646 Mon Sep 17 00:00:00 2001 From: Olivia Thet Date: Fri, 15 Mar 2024 00:46:53 -0400 Subject: [PATCH 9/9] accidentally removed body tag --- export/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/export/index.html b/export/index.html index b774730..296a777 100644 --- a/export/index.html +++ b/export/index.html @@ -647,4 +647,5 @@

Message log

return res } +