UNPKG

0xweb

Version:

Contract package manager and other web3 tools

224 lines (194 loc) 9.04 kB
import { TEth } from '@dequanto/models/TEth'; import { $buffer } from './$buffer'; import { $require } from './$require'; import { $is } from './$is'; interface ICrypto { randomBytes(size: number): Uint8Array; randomUUID(): `${string}-${string}-${string}-${string}-${string}`; createECDH(curve: string) sha256 (mix: string | TEth.Hex | Uint8Array): Promise<Uint8Array>; encrypt(buffer: string | TEth.Hex | Uint8Array, opts: IEncryptionParams & { encoding?: undefined | 'binary' }): Promise<Uint8Array> encrypt(buffer: string | TEth.Hex | Uint8Array, opts: IEncryptionParams & { encoding: 'hex' }): Promise<TEth.Hex> encrypt(buffer: string | TEth.Hex | Uint8Array, opts: IEncryptionParams & { encoding: 'utf8' }): Promise<string> decrypt(buffer: TEth.Hex | Uint8Array, opts: IEncryptionParams & { encoding?: undefined | 'binary' }): Promise<Uint8Array> decrypt(buffer: TEth.Hex | Uint8Array, opts: IEncryptionParams & { encoding: 'hex' }): Promise<TEth.Hex> decrypt(buffer: TEth.Hex | Uint8Array, opts: IEncryptionParams & { encoding: 'utf8' }): Promise<string> } interface IEncryptionParams { secret: string | Uint8Array encoding?: 'binary' | 'hex' | 'utf8' } const CIPHER_ALGO = 'aes-256-ctr'; abstract class CryptoBase implements ICrypto { abstract randomBytes(size: number): Uint8Array; abstract randomUUID(): `${string}-${string}-${string}-${string}-${string}`; abstract createECDH(curve: string) sha256 (mix: string | TEth.Hex | Uint8Array, opts?: { encoding?: undefined | 'binary' }): Promise<Uint8Array> sha256 (mix: string | TEth.Hex | Uint8Array, opts?: { encoding: 'hex' }): Promise<TEth.Hex> async sha256 (mix: string | TEth.Hex | Uint8Array, opts?: { encoding?: 'hex' | 'binary' }) { const buffer = utils.toBuffer(mix); const hash = await this.sha256Inner(buffer); if (opts?.encoding === 'hex') { return $buffer.toHex(hash); } return hash; } encrypt(mix: string | TEth.Hex | Uint8Array, opts: IEncryptionParams & { encoding?: undefined | 'binary' }): Promise<Uint8Array> encrypt(mix: string | TEth.Hex | Uint8Array, opts: IEncryptionParams & { encoding: 'hex' }): Promise<TEth.Hex> encrypt(mix: string | TEth.Hex | Uint8Array, opts: IEncryptionParams & { encoding: 'utf8' }): Promise<string> async encrypt(mix: string | TEth.Hex | Uint8Array, opts: IEncryptionParams) { const buffer = utils.toBuffer(mix); $require.gt(buffer.length, 0, `Buffer to encrypt must be a non-empty`); const secret = opts.secret; $require.gt(secret.length, 0, `Secret must be a non-empty`); const bufferSecret = await this.prepareSecret(secret); let encrypted = await this.encryptInner(buffer, bufferSecret); switch (opts.encoding) { case 'hex': return $buffer.toHex(encrypted); case 'utf8': return $buffer.toString(encrypted); default: return encrypted; } } decrypt(buffer: TEth.Hex | Uint8Array, opts: IEncryptionParams & { encoding?: undefined | 'binary' }): Promise<Uint8Array> decrypt(buffer: TEth.Hex | Uint8Array, opts: IEncryptionParams & { encoding: 'hex' }): Promise<TEth.Hex> decrypt(buffer: TEth.Hex | Uint8Array, opts: IEncryptionParams & { encoding: 'utf8' }): Promise<string> async decrypt(mix: TEth.Hex | Uint8Array, opts: IEncryptionParams) { const buffer = $buffer.ensure(mix); $require.gt(buffer?.length, 0, `Buffer to decrypt must be a non-empty`); const secret = opts.secret; $require.gt(secret.length, 0, `Secret must be a non-empty`); const bufferSecret = await this.prepareSecret(secret); let decrypted = await this.decryptInner(buffer, bufferSecret); switch (opts.encoding) { case 'hex': return $buffer.toHex(decrypted); case 'utf8': return $buffer.toString(decrypted, 'utf8'); default: return decrypted; } } protected abstract encryptInner (buffer: Uint8Array, secret: Uint8Array): Promise<Uint8Array>; protected abstract decryptInner (buffer: Uint8Array, secret: Uint8Array): Promise<Uint8Array>; protected abstract sha256Inner (buffer: Uint8Array): Promise<Uint8Array>; private async prepareSecret (secret: string | TEth.Hex | Uint8Array): Promise<Uint8Array> { if (typeof secret === 'string' && $is.HexBytes32(secret) === false) { let buffer = $buffer.fromString(secret); return await this.sha256Inner(buffer); } return $buffer.ensure(secret); } } namespace WebCryptoImpl { export class CryptoWeb extends CryptoBase { crypto = crypto; randomBytes(size: number) { let array = new Uint8Array(size); let rnd = this.crypto.getRandomValues(array); return rnd; } randomUUID() { return this.crypto.randomUUID() as `${string}-${string}-${string}-${string}-${string}`; } createECDH(curve: string) { /** use this.crypto.subtle.importKey */ throw new Error("Method not implemented."); } async encryptInner(buffer: Uint8Array, secret: Uint8Array): Promise<Uint8Array> { const key = await this._getEncryptionKey(secret); const iv = this.randomBytes(16); const cipherText = await this.crypto.subtle.encrypt( { name: 'AES-GCM', iv, }, key, buffer ); const encrypted = $buffer.concat([iv, new Uint8Array(cipherText)]); return encrypted; } async decryptInner(buffer: Uint8Array, secret: Uint8Array): Promise<Uint8Array> { const key = await this._getEncryptionKey(secret); const iv = buffer.slice(0, 16); const encrypted = buffer.slice(16); const output = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv, }, key, encrypted ); return new Uint8Array(output); } private async _getEncryptionKey(secret: Uint8Array): Promise<CryptoKey> { const secretKey = await this.crypto.subtle.importKey('raw', secret, { name: 'AES-GCM', length: 256, }, true, ['encrypt', 'decrypt'] ); return secretKey; } protected async sha256Inner(buffer: Uint8Array): Promise<Uint8Array> { const arrayBuffer = await crypto.subtle.digest('SHA-256', buffer); return new Uint8Array(arrayBuffer); } } } namespace NodeCryptoImpl { export class CryptoNode extends CryptoBase { crypto: typeof import('crypto') = require('crypto'); randomBytes(size: number) { const bytes = this.crypto.randomBytes(size); return bytes; } randomUUID() { return this.crypto.randomUUID() as `${string}-${string}-${string}-${string}-${string}`; } createECDH(curve: string) { return this.crypto.createECDH(curve); } protected async encryptInner(buffer: Uint8Array, secret: Uint8Array): Promise<Uint8Array> { $require.gt(buffer.length, 0, `Buffer to encrypt must be a non-empty`); const key = secret; const iv = this.crypto.randomBytes(16); const cipher = this.crypto.createCipheriv(CIPHER_ALGO, key, iv); const cipherText = cipher.update(buffer); const encrypted = $buffer.concat([iv, cipherText, cipher.final()]); return encrypted; } protected async decryptInner(buffer: Uint8Array, secret: Uint8Array): Promise<Uint8Array> { const key = secret; const iv = buffer.slice(0, 16); const decipher = this.crypto.createDecipheriv(CIPHER_ALGO, key, iv); const cipherBuf = buffer.slice(16); const output = $buffer.concat([decipher.update(cipherBuf), decipher.final()]); return output; } protected async sha256Inner(buffer: Uint8Array): Promise<Uint8Array> { return this.crypto.createHash('sha256').update(buffer).digest() } } } export const $crypto: ICrypto = typeof crypto === "undefined" ? new NodeCryptoImpl.CryptoNode() : new WebCryptoImpl.CryptoWeb() ; export const $cryptoImpl = { Web: WebCryptoImpl.CryptoWeb, Node: NodeCryptoImpl.CryptoNode, }; namespace utils { export function toBuffer (mix: string | TEth.Hex | Uint8Array) { if (typeof mix === 'string' && $is.HexBytes32(mix) === false) { return $buffer.fromString(mix); } return $buffer.ensure(mix); } }