mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-22 08:28:10 -07:00
global: private xpub support part 2
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
const ENCRYPTION_VERSION = 1;
|
||||
const PBKDF2_ITERATIONS = 250_000;
|
||||
const KEY_BITS = 256;
|
||||
const SALT_BYTES = 16;
|
||||
const IV_BYTES = 12;
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
/**
|
||||
* @typedef {Object} EncryptedSecret
|
||||
* @property {1} version
|
||||
* @property {"PBKDF2-SHA256"} kdf
|
||||
* @property {number} iterations
|
||||
* @property {"AES-GCM"} cipher
|
||||
* @property {string} salt
|
||||
* @property {string} iv
|
||||
* @property {string} ciphertext
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} bytes
|
||||
*/
|
||||
function toArrayBuffer(bytes) {
|
||||
const buffer = new ArrayBuffer(bytes.byteLength);
|
||||
|
||||
new Uint8Array(buffer).set(bytes);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} bytes
|
||||
*/
|
||||
function bytesToBase64(bytes) {
|
||||
let binary = "";
|
||||
|
||||
for (const byte of bytes) {
|
||||
binary += String.fromCharCode(byte);
|
||||
}
|
||||
|
||||
return btoa(binary);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} base64
|
||||
*/
|
||||
function base64ToBytes(base64) {
|
||||
const binary = atob(base64);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
|
||||
for (let i = 0; i < binary.length; i += 1) {
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} length
|
||||
*/
|
||||
function randomBytes(length) {
|
||||
const bytes = new Uint8Array(length);
|
||||
|
||||
crypto.getRandomValues(bytes);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} password
|
||||
*/
|
||||
async function importPassword(password) {
|
||||
return crypto.subtle.importKey(
|
||||
"raw",
|
||||
encoder.encode(password),
|
||||
"PBKDF2",
|
||||
false,
|
||||
["deriveKey"],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} password
|
||||
* @param {Uint8Array} salt
|
||||
* @param {number} iterations
|
||||
*/
|
||||
async function deriveKey(password, salt, iterations) {
|
||||
const key = await importPassword(password);
|
||||
|
||||
return crypto.subtle.deriveKey(
|
||||
{
|
||||
name: "PBKDF2",
|
||||
hash: "SHA-256",
|
||||
salt: toArrayBuffer(salt),
|
||||
iterations,
|
||||
},
|
||||
key,
|
||||
{
|
||||
name: "AES-GCM",
|
||||
length: KEY_BITS,
|
||||
},
|
||||
false,
|
||||
["encrypt", "decrypt"],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} secret
|
||||
* @param {string} password
|
||||
* @returns {Promise<EncryptedSecret>}
|
||||
*/
|
||||
async function encrypt(secret, password) {
|
||||
const salt = randomBytes(SALT_BYTES);
|
||||
const iv = randomBytes(IV_BYTES);
|
||||
const key = await deriveKey(password, salt, PBKDF2_ITERATIONS);
|
||||
const encrypted = await crypto.subtle.encrypt(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
iv: toArrayBuffer(iv),
|
||||
},
|
||||
key,
|
||||
encoder.encode(secret),
|
||||
);
|
||||
|
||||
return {
|
||||
version: ENCRYPTION_VERSION,
|
||||
kdf: "PBKDF2-SHA256",
|
||||
iterations: PBKDF2_ITERATIONS,
|
||||
cipher: "AES-GCM",
|
||||
salt: bytesToBase64(salt),
|
||||
iv: bytesToBase64(iv),
|
||||
ciphertext: bytesToBase64(new Uint8Array(encrypted)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {EncryptedSecret} encrypted
|
||||
* @param {string} password
|
||||
*/
|
||||
async function decrypt(encrypted, password) {
|
||||
if (encrypted.version !== ENCRYPTION_VERSION) {
|
||||
throw new Error("Unsupported wallet encryption version");
|
||||
}
|
||||
|
||||
const salt = base64ToBytes(encrypted.salt);
|
||||
const iv = base64ToBytes(encrypted.iv);
|
||||
const ciphertext = base64ToBytes(encrypted.ciphertext);
|
||||
const key = await deriveKey(password, salt, encrypted.iterations);
|
||||
const decrypted = await crypto.subtle.decrypt(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
iv: toArrayBuffer(iv),
|
||||
},
|
||||
key,
|
||||
toArrayBuffer(ciphertext),
|
||||
);
|
||||
|
||||
return decoder.decode(decrypted);
|
||||
}
|
||||
|
||||
export const encryption = /** @type {const} */ ({
|
||||
encrypt,
|
||||
decrypt,
|
||||
});
|
||||
Reference in New Issue
Block a user