feat(vault): add get/create assets API in workflow
TODO: update vault.service.ts. Use MNEMONIC workflow to create real private_key_shard and content_inner_encrypted
This commit is contained in:
107
src/utils/vaultCrypto.ts
Normal file
107
src/utils/vaultCrypto.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Vault crypto: PBKDF2 key derivation + AES-256-GCM encrypt/decrypt.
|
||||
* Matches backend SentinelVault semantics (PBKDF2 from mnemonic, AES-GCM).
|
||||
* Uses Web Crypto API (crypto.subtle). Requires secure context / React Native polyfill if needed.
|
||||
*/
|
||||
|
||||
const SALT = new TextEncoder().encode('Sentinel_Salt_2026');
|
||||
const PBKDF2_ITERATIONS = 100000;
|
||||
const AES_KEY_LEN = 256;
|
||||
const GCM_IV_LEN = 16;
|
||||
const GCM_TAG_LEN = 16;
|
||||
|
||||
function getCrypto(): Crypto {
|
||||
if (typeof crypto !== 'undefined' && crypto.subtle) return crypto;
|
||||
throw new Error('vaultCrypto: crypto.subtle not available');
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a 32-byte AES key from mnemonic phrase (space-separated words).
|
||||
*/
|
||||
export async function deriveKey(mnemonicPhrase: string, salt: Uint8Array = SALT): Promise<ArrayBuffer> {
|
||||
const crypto = getCrypto();
|
||||
const keyMaterial = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
new TextEncoder().encode(mnemonicPhrase),
|
||||
'PBKDF2',
|
||||
false,
|
||||
['deriveBits']
|
||||
);
|
||||
const saltBuf = salt.buffer.slice(salt.byteOffset, salt.byteOffset + salt.byteLength) as ArrayBuffer;
|
||||
const bits = await crypto.subtle.deriveBits(
|
||||
{
|
||||
name: 'PBKDF2',
|
||||
salt: saltBuf,
|
||||
iterations: PBKDF2_ITERATIONS,
|
||||
hash: 'SHA-256',
|
||||
},
|
||||
keyMaterial,
|
||||
AES_KEY_LEN
|
||||
);
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt plaintext with AES-256-GCM. Returns nonce(16) + tag(16) + ciphertext (matches Python SentinelVault).
|
||||
*/
|
||||
export async function encryptDataGCM(key: ArrayBuffer, plaintext: string): Promise<Uint8Array> {
|
||||
const crypto = getCrypto();
|
||||
const iv = crypto.getRandomValues(new Uint8Array(GCM_IV_LEN));
|
||||
const cryptoKey = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
key,
|
||||
{ name: 'AES-GCM' },
|
||||
false,
|
||||
['encrypt']
|
||||
);
|
||||
const encoded = new TextEncoder().encode(plaintext);
|
||||
const ciphertextWithTag = await crypto.subtle.encrypt(
|
||||
{ name: 'AES-GCM', iv, tagLength: GCM_TAG_LEN * 8 },
|
||||
cryptoKey,
|
||||
encoded
|
||||
);
|
||||
const out = new Uint8Array(iv.length + ciphertextWithTag.byteLength);
|
||||
out.set(iv, 0);
|
||||
out.set(new Uint8Array(ciphertextWithTag), iv.length);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt blob from encryptDataGCM (nonce(16) + ciphertext+tag).
|
||||
*/
|
||||
export async function decryptDataGCM(key: ArrayBuffer, blob: Uint8Array): Promise<string> {
|
||||
const crypto = getCrypto();
|
||||
const iv = blob.subarray(0, GCM_IV_LEN);
|
||||
const ciphertextWithTag = blob.subarray(GCM_IV_LEN);
|
||||
const ivBuf = iv.buffer.slice(iv.byteOffset, iv.byteOffset + iv.byteLength) as ArrayBuffer;
|
||||
const ctBuf = ciphertextWithTag.buffer.slice(
|
||||
ciphertextWithTag.byteOffset,
|
||||
ciphertextWithTag.byteOffset + ciphertextWithTag.byteLength
|
||||
) as ArrayBuffer;
|
||||
const cryptoKey = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
key,
|
||||
{ name: 'AES-GCM' },
|
||||
false,
|
||||
['decrypt']
|
||||
);
|
||||
const dec = await crypto.subtle.decrypt(
|
||||
{ name: 'AES-GCM', iv: ivBuf, tagLength: GCM_TAG_LEN * 8 },
|
||||
cryptoKey,
|
||||
ctBuf
|
||||
);
|
||||
return new TextDecoder().decode(dec);
|
||||
}
|
||||
|
||||
export function bytesToHex(bytes: Uint8Array): string {
|
||||
return Array.from(bytes)
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
}
|
||||
|
||||
export function hexToBytes(hex: string): Uint8Array {
|
||||
const len = hex.length / 2;
|
||||
const out = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
||||
return out;
|
||||
}
|
||||
Reference in New Issue
Block a user