import BufferReader from 'buffer-reader' ; import EC from 'elliptic' ; import crypto from 'crypto' ; const ec = new EC . ec ( 'secp256k1' ) ; export default class RadixECIES { public static decrypt ( privKey , encrypted ) { const reader = new BufferReader ( encrypted ) ; const iv = reader . nextBuffer ( 16 ) ; const ephemPubKeyEncoded = reader . nextBuffer ( reader . nextUInt8 ( ) ) ; const ciphertext = reader . nextBuffer ( reader . nextUInt32BE ( ) ) ; const mac = reader . nextBuffer ( 32 ) ; const ephemPubKey = ec . keyFromPublic ( ephemPubKeyEncoded ) . getPublic ( ) ; const px = ec . keyFromPrivate ( privKey ) . derive ( ephemPubKey ) ; const hash = crypto . createHash ( 'sha512' ) . update ( crypto . createHash ( 'sha512' ) . update ( px . toArrayLike ( Buffer ) ) . digest ( ) ) . digest ( ) ; const encryptionKey = hash . slice ( 0 , 32 ) ; const macKey = hash . slice ( 32 ) ; const computedMAC = this . calculateMAC ( macKey , iv , ephemPubKeyEncoded , ciphertext ) ; if ( ! computedMAC . equals ( mac ) ) { throw new Error ( 'mac mismatch' ) ; } const plaintext = this . AES256CbcDecrypt ( iv , encryptionKey , ciphertext ) ; return plaintext ; } public static encrypt ( pubKeyTo , plaintext ) { const ephemPrivKey = ec . keyFromPrivate ( crypto . randomBytes ( 32 ) ) ; const ephemPubKey = ephemPrivKey . getPublic ( ) ; const ephemPubKeyEncoded = Buffer . from ( ephemPubKey . encode ( 'array' , true ) ) ; const px = ephemPrivKey . derive ( ec . keyFromPublic ( pubKeyTo ) . getPublic ( ) ) ; const hash = crypto . createHash ( 'sha512' ) . update ( crypto . createHash ( 'sha512' ) . update ( px . toArrayLike ( Buffer ) ) . digest ( ) ) . digest ( ) ; const iv = crypto . randomBytes ( 16 ) ; const encryptionKey = hash . slice ( 0 , 32 ) ; const macKey = hash . slice ( 32 ) ; const ciphertext = this . AES256CbcEncrypt ( iv , encryptionKey , plaintext ) ; const mac = this . calculateMAC ( macKey , iv , ephemPubKeyEncoded , ciphertext ) ; let offset = 0 ; const serializedCiphertext = Buffer . alloc ( iv . length + 1 + ephemPubKeyEncoded . length + 4 + ciphertext . length + mac . length ) ; iv . copy ( serializedCiphertext , 0 ) ; offset += iv . length ; serializedCiphertext . writeUInt8 ( ephemPubKeyEncoded . length , offset ) ; offset ++ ; ephemPubKeyEncoded . copy ( serializedCiphertext , offset ) ; offset += ephemPubKeyEncoded . length ; serializedCiphertext . writeUInt32BE ( ciphertext . length , offset ) ; offset += 4 ; ciphertext . copy ( serializedCiphertext , offset ) ; offset += ciphertext . length ; mac . copy ( serializedCiphertext , offset ) ; return serializedCiphertext ; } public static calculateMAC ( macKey , iv , ephemPubKeyEncoded , ciphertext ) { const dataToMAC = Buffer . concat ( [ iv , ephemPubKeyEncoded , ciphertext ] ) ; return crypto . createHmac ( 'sha256' , macKey ) . update ( dataToMAC ) . digest ( ) ; } param { Buffer } iv param { Buffer } key param { Buffer } plaintext returns { Buffer } public static AES256CbcEncrypt = ( iv , key , plaintext ) => { const cipher = crypto . createCipheriv ( 'aes-256-cbc' , key , iv ) ; const firstChunk = cipher . update ( plaintext ) ; const secondChunk = cipher . final ( ) ; return Buffer . concat ( [ firstChunk , secondChunk ] ) ; } ; param { Buffer } iv param { Buffer } key param { Buffer } ciphertext returns { Buffer } public static AES256CbcDecrypt = ( iv , key , ciphertext ) => { const cipher = crypto . createDecipheriv ( 'aes-256-cbc' , key , iv ) ; const firstChunk = cipher . update ( ciphertext ) ; const secondChunk = cipher . final ( ) ; return Buffer . concat ( [ firstChunk , secondChunk ] ) ; } ; public static encryptForMultiple ( recipientsPublicKeys , plaintext ) { const ephemeral = ec . genKeyPair ( ) ; const ephemeralPriv = Buffer . from ( ephemeral . getPrivate ( 'hex' ) , 'hex' ) ; const protectors = recipientsPublicKeys . map ( ( recipient ) => { return this . encrypt ( recipient , ephemeralPriv ) ; } ) ; const ciphertext = RadixECIES . encrypt ( Buffer . from ( ephemeral . getPublic ( true , 'array' ) ) , plaintext ) ; return { protectors , ciphertext , } ; } public static decryptWithProtectors ( privKey , protectors , ciphertext ) { for ( const protector of protectors ) { try { const ephemPrivKey = this . decrypt ( privKey , protector ) ; try { return this . decrypt ( ephemPrivKey , ciphertext ) ; } catch ( error ) { throw new Error ( 'Decrypted a protector, but unable to decrypt ciphertext with acquired private key' ) ; } } catch ( error ) { } } throw new Error ( 'Unable to decrypt any protectors' ) ; } }