import { VerificationMethod } from 'did-resolver/src/resolver' ; import { DIDDocument } from 'did-resolver' ; import { CEKData } from '../solana/models/CEKData' ; import { isPublicKey , makeKeypair , PrivateKey } from '../util' ; import { randomBytes } from '@stablelib/random' ; import { x25519xc20pKeyWrap , x25519xc20pKeyUnwrap , XC20P_IV_LENGTH , XC20P_TAG_LENGTH , xc20pDecrypter , xc20pEncrypter , } from './xc20pEncryption' ; import * as u8a from 'uint8arrays' ; import { convertPublicKey , convertSecretKey } from 'ed2curve-esm' ; import { PublicKey } from '@solana/web3.js' ; import { base64ToBytes , bytesToBase64 , bytesToString , stringToBytes , base58ToBytes , bytesToBase58 , } from './utils' ; import { getDocument } from '../did/get' ; export type CEK = Uint8Array ; const shortenKID = ( kid ) : string => kid . substring ( kid . indexOf ( '#' ) + 1 ) ; export const generateCEK = async ( ) : Promise < CEK > => { const cek = randomBytes ( 32 ) ; return Promise . resolve ( cek ) ; } ; const getVerificationMethod = ( vmOrRef , document ) : VerificationMethod => { if ( Object . prototype . hasOwnProperty . call ( vmOrRef , 'id' ) ) return vmOrRef as VerificationMethod ; const foundKey = ( document . verificationMethod || [ ] ) . find ( key => key . id === vmOrRef ) ; if ( ! foundKey ) throw new Error ( `Missing key ${ vmOrRef }` ) ; return foundKey ; } ; export const encryptCEKForDID = async ( cek , did ) : Promise < CEKData [ ] > => { const didDocument = await getDocument ( did ) ; const augmentedDIDDocument = augmentDIDDocument ( didDocument ) ; return encryptCEKForDIDDocument ( cek , augmentedDIDDocument ) ; } ; export const encryptCEKForVerificationMethod = async ( cek , key ) : Promise < CEKData > => { if ( ! key . publicKeyBase58 ) { throw Error ( 'Currently we expect the recipient key to be encoded as base58' ) ; } const res = await x25519xc20pKeyWrap ( base58ToBytes ( key . publicKeyBase58 ) ) ( cek ) ; const concatByteArray = u8a . concat ( [ res . iv , res . tag , res . epPubKey ] ) ; const header = bytesToBase64 ( concatByteArray ) ; return new CEKData ( { kid : shortenKID ( key . id ) , header , encryptedKey : bytesToBase64 ( res . encryptedKey ) , } ) ; } ; export const encryptCEKForDIDDocument = async ( cek , didDocument ) : Promise < CEKData [ ] > => { const encryptedCEKPromises = ( didDocument . keyAgreement || [ ] ) . map ( async ( keyOrRef ) : Promise < CEKData > => { const verificationMethod = getVerificationMethod ( keyOrRef , didDocument ) ; return encryptCEKForVerificationMethod ( cek , verificationMethod ) ; } ) ; return Promise . all ( encryptedCEKPromises ) ; } ; export const createEncryptedCEK = async ( did ) : Promise < CEKData [ ] > => { const cek = await generateCEK ( ) ; return encryptCEKForDID ( cek , did ) ; } ; export const decryptCEK = async ( encryptedCEK , key ) : Promise < CEK > => { const encodedHeader = base64ToBytes ( encryptedCEK . header ) ; const iv = encodedHeader . subarray ( 0 , XC20P_IV_LENGTH ) ; const tag = encodedHeader . subarray ( XC20P_IV_LENGTH , XC20P_IV_LENGTH + XC20P_TAG_LENGTH ) ; const epkPub = encodedHeader . subarray ( XC20P_IV_LENGTH + XC20P_TAG_LENGTH ) ; const encryptedKey = base64ToBytes ( encryptedCEK . encryptedKey ) ; const ed25519Key = makeKeypair ( key ) . secretKey ; const curve25519Key = convertSecretKey ( ed25519Key ) ; const cek = await x25519xc20pKeyUnwrap ( curve25519Key ) ( encryptedKey , tag , iv , epkPub ) ; if ( cek === null ) { throw Error ( 'There was a problem decrypting the CEK' ) ; } return cek ; } ; export const decryptCEKs = async ( encryptedCEKs , kid , key ) : Promise < CEK > => { const encryptedCEK = encryptedCEKs . find ( k => k . kid === shortenKID ( kid ) || k . kid === kid ) ; if ( ! encryptedCEK ) throw new Error ( `No encrypted CEK found for key ${ kid }` ) ; return decryptCEK ( encryptedCEK , key ) ; } ; export const encryptMessage = async ( message , cek ) : Promise < string > => { const encryptMessage = await xc20pEncrypter ( cek ) ( stringToBytes ( message ) ) ; const encodedEncMessage = bytesToBase64 ( u8a . concat ( [ encryptMessage . iv , encryptMessage . ciphertext , encryptMessage . tag , ] ) ) ; return encodedEncMessage ; } ; export const decryptMessage = async ( encryptedMessage , cek ) : Promise < string > => { const encMessage = base64ToBytes ( encryptedMessage ) ; const iv = encMessage . subarray ( 0 , XC20P_IV_LENGTH ) ; const ciphertext = encMessage . subarray ( XC20P_IV_LENGTH , - XC20P_TAG_LENGTH ) ; const tag = encMessage . subarray ( - XC20P_TAG_LENGTH ) ; const binMessage = await xc20pDecrypter ( cek ) ( ciphertext , tag , iv ) ; if ( binMessage === null ) { throw new Error ( 'There was an error decoding the message!' ) ; } const message = bytesToString ( binMessage ) ; return message ; } ; export const findVerificationMethodForKey = ( didDoc , key ) : VerificationMethod | undefined => { const augmentedDIDDoc = augmentDIDDocument ( didDoc ) ; if ( ! isPublicKey ( key ) ) { const keypair = makeKeypair ( key ) ; key = keypair . publicKey ; } const pubkey = bytesToBase58 ( convertPublicKey ( key . toBytes ( ) ) ) ; const foundVerificationMethod = ( augmentedDIDDoc . verificationMethod || [ ] ) . find ( verificationMethod => verificationMethod . publicKeyBase58 === pubkey ) ; return foundVerificationMethod ; } ; export const reencryptCEKForDID = async ( encryptedCEK , fromDID , fromPrivateKey , toDID ) : Promise < CEKData [ ] > => { const fromDIDDocument = await getDocument ( fromDID ) ; const fromVerificationMethod = findVerificationMethodForKey ( fromDIDDocument , fromPrivateKey ) ; if ( ! fromVerificationMethod ) throw new Error ( 'Private key does not match any Verification Method in the DID' ) ; const cek = await decryptCEKs ( encryptedCEK , fromVerificationMethod . id , fromPrivateKey ) ; return encryptCEKForDID ( cek , toDID ) ; } ; export const augmentDIDDocument = ( didDocument ) : DIDDocument => { if ( didDocument . keyAgreement && didDocument . keyAgreement . length ) return didDocument ; if ( ! didDocument . verificationMethod || ! didDocument . verificationMethod . length ) { throw Error ( 'Cannot augment DID document for x25519. The document has no keys' ) ; } const keyAgreementKeys = didDocument . verificationMethod . filter ( key => key . type === 'Ed25519VerificationKey2018' ) . filter ( key => ! ! key . publicKeyBase58 ) . map ( key => ( { ... key , id : key . id + '_keyAgreement' , type : 'X25519KeyAgreementKey2019' , publicKeyBase58 : bytesToBase58 ( convertPublicKey ( base58ToBytes ( key . publicKeyBase58 as string ) ) ) , } ) ) ; return { ... didDocument , publicKey : [ ... didDocument . verificationMethod , ... keyAgreementKeys ] , verificationMethod : [ ... didDocument . verificationMethod , ... keyAgreementKeys , ] , keyAgreement : keyAgreementKeys . map ( key => key . id ) , } ; } ;