UNPKG

wowok

Version:

Wowok Blockchain TypeScript API

1 lines 7.98 kB
import{generateX25519KeyPair,concatBytes,deriveRegistrationId,bytesToBase64,base64ToBytes}from'./crypto.js';import{pqxdhInitiate,pqxdhReceive,generatePreKeyBundle,verifyPreKeyBundle}from'./pqxdh.js';import{initRatchet,ratchetEncrypt,ratchetDecrypt,serializeRatchetState,deserializeRatchetState}from'./ratchet.js';import{initSPQR,spqrGetNextChunk,spqrReceiveChunk,spqrOnPeerPQPKReceived,serializeSPQRState,deserializeSPQRState}from'./spqr.js';import{packChunk,SHARD_SIZE}from'./erasure.js';import{MessageType,DEFAULT_DEVICE_ID,CHAIN_KEY_SIZE,PQError,PQErrorCode}from'./types.js';export class PQSessionCipher{['store'];['identity']=null;['getFalcon512KeyPair'];constructor(a,b){this['store']=a,this['getFalcon512KeyPair']=b||(async()=>{throw new PQError(PQErrorCode['HANDSHAKE_FAILED'],'Falcon512\x20key\x20pair\x20not\x20available\x20for\x20signing');});}async['ensureIdentity'](){if(this['identity'])return this['identity'];let a=await this['store']['getIdentityKeyPair'](),b=await this['store']['getRegistrationId']();return(!a||!b)&&(a=generateX25519KeyPair(),b=deriveRegistrationId(a['publicKey']),await this['store']['setIdentityKeyPair'](a),await this['store']['setRegistrationId'](b)),this['identity']={'x25519KeyPair':a,'registrationId':b},this['identity'];}async['generatePreKeyBundle'](a,b,c){const d=await this['ensureIdentity'](),e=await this['getFalcon512KeyPair'](),f=generatePreKeyBundle(d['x25519KeyPair'],e,a,b,c);await this['store']['setSignedPreKey'](a,f['signedPreKey']['keyPair']),await this['store']['setSignedPreKeySignature'](a,f['signedPreKey']['signature']);for(const g of f['oneTimePreKeys']){await this['store']['setOneTimePreKey'](g['keyId'],g['keyPair']);}return await this['store']['setPQPreKey'](b,f['pqPreKey']['keyPair']),await this['store']['setPQPreKeySignature'](b,f['pqPreKey']['signature']),f;}async['processPreKeyBundle'](a,b,c){const d=await this['ensureIdentity']();if(!verifyPreKeyBundle(c))throw new PQError(PQErrorCode['HANDSHAKE_FAILED'],'PreKey\x20bundle\x20signature\x20verification\x20failed');const e=pqxdhInitiate(d['x25519KeyPair'],c),f=initRatchet(e['result']['rootKey'],e['result']['chainKey'],new Uint8Array(CHAIN_KEY_SIZE),e['result']['ourDHKeyPair'],e['result']['peerDHPublicKey']),g=initSPQR(),h={'pqxdhComplete':!![],'ratchetState':f,'spqrState':g,'peerIdentityKey':c['identityKey'],'peerRegistrationId':c['registrationId'],'peerDeviceId':b,'pendingInitialMessage':{'ephemeralPublicKey':e['initialMessage']['ephemeralPublicKey'],'mlkemCiphertext':e['initialMessage']['mlkemCiphertext'],'identityKey':e['initialMessage']['identityKey'],'signedPreKeyId':c['signedPreKey']['keyId'],'oneTimePreKeyId':c['oneTimePreKey']?.['keyId']??null,'pqPreKeyId':c['pqPreKey']['keyId']}};await this['store']['setPeerIdentity'](a,c['identityKey']),await this['saveSession'](a,b,h);}async['processInitialMessage'](a,b,c,d,e,f,g,h){const i=await this['ensureIdentity'](),j=await this['store']['getSignedPreKey'](f);if(!j)throw new PQError(PQErrorCode['HANDSHAKE_FAILED'],'Signed\x20pre-key\x20not\x20found');let k=null;g!==null&&(k=await this['store']['getOneTimePreKey'](g),k&&await this['store']['removeOneTimePreKey'](g));const l=await this['store']['getPQPreKey'](h);if(!l)throw new PQError(PQErrorCode['HANDSHAKE_FAILED'],'PQ\x20pre-key\x20not\x20found');const m=pqxdhReceive(i['x25519KeyPair'],j,k,l,c,e,d),n=initRatchet(m['rootKey'],new Uint8Array(CHAIN_KEY_SIZE),m['chainKey'],m['ourDHKeyPair'],m['peerDHPublicKey']),o=initSPQR(),p={'pqxdhComplete':!![],'ratchetState':n,'spqrState':o,'peerIdentityKey':e,'peerRegistrationId':0x0,'peerDeviceId':b};await this['store']['setPeerIdentity'](a,e),await this['saveSession'](a,b,p);}async['encryptMessage'](a,b,c=DEFAULT_DEVICE_ID){const d=await this['loadSession'](a,c);if(!d)throw new PQError(PQErrorCode['SESSION_NOT_FOUND'],'No\x20session\x20established');const {message:e,newState:f}=ratchetEncrypt(d['ratchetState'],b),g=f['sendingMessageCount']+f['previousSendingCount'],{chunk:h,newState:i}=spqrGetNextChunk(d['spqrState'],g);let j;if(h){const l=packChunk(h);j=concatBytes(new Uint8Array([0x1]),e['body'],l);}else j=concatBytes(new Uint8Array([0x0]),e['body']);const k=d['pendingInitialMessage']!==undefined;if(d['pendingInitialMessage']){const m=d['pendingInitialMessage'],n=m['oneTimePreKeyId']??0xffffffff,o=new Uint8Array(0x20+0x440+0x20+0x4+0x4+0x4),p=new DataView(o['buffer']);o['set'](m['ephemeralPublicKey'],0x0),o['set'](m['mlkemCiphertext'],0x20),o['set'](m['identityKey'],0x20+0x440),p['setUint32'](0x20+0x440+0x20,m['signedPreKeyId'],![]),p['setUint32'](0x20+0x440+0x20+0x4,n,![]),p['setUint32'](0x20+0x440+0x20+0x8,m['pqPreKeyId'],![]),j=concatBytes(o,j),d['pendingInitialMessage']=undefined;}return d['ratchetState']=f,d['spqrState']=i,await this['saveSession'](a,c,d),{'type':k?MessageType['PREKEY_MESSAGE']:MessageType['NORMAL_MESSAGE'],'body':j};}async['decryptMessage'](a,b,c=DEFAULT_DEVICE_ID){const d=0x20+0x440+0x20+0x4+0x4+0x4;let e=await this['loadSession'](a,c);if(!e&&b['length']>=d){const m=new DataView(b['buffer'],b['byteOffset'],b['byteLength']),n=b['slice'](0x0,0x20),o=b['slice'](0x20,0x20+0x440),p=b['slice'](0x20+0x440,0x20+0x440+0x20),q=m['getUint32'](0x20+0x440+0x20,![]),r=m['getUint32'](0x20+0x440+0x20+0x4,![]),s=m['getUint32'](0x20+0x440+0x20+0x8,![]),t=r===0xffffffff?null:r;await this['processInitialMessage'](a,c,n,o,p,q,t,s),b=b['slice'](d),e=await this['loadSession'](a,c);}if(!e)throw new PQError(PQErrorCode['SESSION_NOT_FOUND'],'No\x20session\x20established');if(b['length']<0x1)throw new PQError(PQErrorCode['DECRYPTION_FAILED'],'Message\x20body\x20too\x20short');const f=b[0x0]===0x1,g=b['slice'](0x1);let h=null;if(f){if(g['length']<SHARD_SIZE)throw new PQError(PQErrorCode['DECRYPTION_FAILED'],'SPQR\x20chunk\x20missing');h=g['slice'](g['length']-SHARD_SIZE);}const i=f?g['slice'](0x0,g['length']-SHARD_SIZE):g,{plaintext:j,newState:k}=ratchetDecrypt(e['ratchetState'],i);let l=e['spqrState'];if(h){const u=spqrReceiveChunk(l,h);l=u['newState'];if(u['shouldSendCT']&&u['peerPQPK']){const v=spqrOnPeerPQPKReceived(l,u['peerPQPK']);l=v['newState'];}}return e['ratchetState']=k,e['spqrState']=l,await this['saveSession'](a,c,e),j;}async['hasSession'](a,b=DEFAULT_DEVICE_ID){const c=await this['loadSession'](a,b);return c!==null&&c['pqxdhComplete'];}async['loadSession'](a,b){const c=await this['store']['getSession'](a,b);if(!c)return null;const d={'pqxdhComplete':c['pqxdhComplete'],'ratchetState':deserializeRatchetState(c['ratchetState']),'spqrState':deserializeSPQRState(c['spqrState']),'peerIdentityKey':base64ToBytes(c['peerIdentityKey']),'peerRegistrationId':c['peerRegistrationId'],'peerDeviceId':c['peerDeviceId']};return c['pendingInitialMessage']&&(d['pendingInitialMessage']={'ephemeralPublicKey':base64ToBytes(c['pendingInitialMessage']['ephemeralPublicKey']),'mlkemCiphertext':base64ToBytes(c['pendingInitialMessage']['mlkemCiphertext']),'identityKey':base64ToBytes(c['pendingInitialMessage']['identityKey']),'signedPreKeyId':c['pendingInitialMessage']['signedPreKeyId'],'oneTimePreKeyId':c['pendingInitialMessage']['oneTimePreKeyId'],'pqPreKeyId':c['pendingInitialMessage']['pqPreKeyId']}),d;}async['saveSession'](a,b,c){const d={'pqxdhComplete':c['pqxdhComplete'],'ratchetState':serializeRatchetState(c['ratchetState']),'spqrState':serializeSPQRState(c['spqrState']),'peerIdentityKey':bytesToBase64(c['peerIdentityKey']),'peerRegistrationId':c['peerRegistrationId'],'peerDeviceId':c['peerDeviceId']};c['pendingInitialMessage']&&(d['pendingInitialMessage']={'ephemeralPublicKey':bytesToBase64(c['pendingInitialMessage']['ephemeralPublicKey']),'mlkemCiphertext':bytesToBase64(c['pendingInitialMessage']['mlkemCiphertext']),'identityKey':bytesToBase64(c['pendingInitialMessage']['identityKey']),'signedPreKeyId':c['pendingInitialMessage']['signedPreKeyId'],'oneTimePreKeyId':c['pendingInitialMessage']['oneTimePreKeyId'],'pqPreKeyId':c['pendingInitialMessage']['pqPreKeyId']}),await this['store']['setSession'](a,b,d);}}