Last active
October 22, 2019 13:42
-
-
Save bonustrack/379d6e46f2f2d5b92440b02db4e0f6a3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const crypto = require('crypto'); | |
const Mnemonic = require('bitcore-mnemonic'); | |
const { publicKeyCreate } = require('secp256k1'); | |
const objectHash = require('@obyte/ocore/object_hash'); | |
const { Client } = require('obyte/lib'); | |
const { sign } = require('obyte/lib/internal'); | |
const { fromWif, toWif } = require('obyte/lib/utils'); | |
// Settings | |
const testnet = false; | |
const wif = '5JQj4efvwU1QEgGF2zeNR1h9qt57cSQPKnagpb2webRjK2MEU3e'; | |
const deviceTempPrivKey = Buffer.from('dnyTw184srJ1P6+yzpyBMhp4VlthUmZvSTFWCVsmkPQ=', 'base64'); | |
const devicePrevTempPrivKey = Buffer.from('+W+gcONkYfB8WV8uVVtozasQPyLELuXIKFo6N0Evbk0=', 'base64'); | |
const recipientDevicePubkey = 'A9NHouN6XYjkmwauIWDV3nFVvshBmeTEuKCT9i77aNbJ'; | |
// Or generate keys | |
/** | |
const seed = 'bla bla bla...'; | |
const passphrase = 'hello world'; | |
const mnemonic = new Mnemonic(seed); | |
const xPrivKey = mnemonic.toHDPrivateKey(passphrase); | |
const devicePrivKey = xPrivKey.derive("m/1'").privateKey.bn.toBuffer({ size: 32 }); | |
const wif = toWif(devicePrivKey, false); | |
const deviceTempPrivKey = crypto.randomBytes(32); | |
const devicePrevTempPrivKey = crypto.randomBytes(32); | |
*/ | |
// Send a message | |
const devicePrivKey = fromWif(wif, testnet).privateKey; | |
const devicePubKey = publicKeyCreate(devicePrivKey, true).toString('base64'); | |
const objMyTempDeviceKey = { use_count: null, priv: deviceTempPrivKey, pub_b64: publicKeyCreate(deviceTempPrivKey, true).toString('base64') }; | |
const objMyPrevTempDeviceKey = { priv: devicePrevTempPrivKey, pub_b64: publicKeyCreate(devicePrevTempPrivKey, true).toString('base64') }; | |
const myDeviceAddress = objectHash.getDeviceAddress(devicePubKey); | |
const objMyPermanentDeviceKey = { priv: devicePrivKey, pub_b64: devicePubKey }; | |
const recipientDeviceAddress = objectHash.getDeviceAddress(recipientDevicePubkey); | |
const client = new Client(); | |
client.subscribe((err, result) => { | |
if (result[0] === 'justsaying') { | |
switch (result[1].subject) { | |
case 'hub/challenge': { | |
const challenge = result[1].body; | |
console.log('Challenge', challenge); | |
const objLogin = { challenge, pubkey: objMyPermanentDeviceKey.pub_b64 }; | |
objLogin.signature = sign( | |
objectHash.getDeviceMessageHashToSign(objLogin), | |
objMyPermanentDeviceKey.priv, | |
); | |
client.justsaying('hub/login', objLogin); | |
const objTempPubkey = { | |
temp_pubkey: objMyTempDeviceKey.pub_b64, | |
pubkey: objMyPermanentDeviceKey.pub_b64, | |
}; | |
objTempPubkey.signature = sign(objectHash.getDeviceMessageHashToSign(objTempPubkey), objMyPermanentDeviceKey.priv); | |
client.api.tempPubkey(objTempPubkey) | |
.then(result => console.log('Temp pubkey result', result)) | |
.catch(e => console.log('Temp pubkey error', e)); | |
client.justsaying('hub/refresh', null); | |
break; | |
} | |
case 'hub/message': { | |
try { | |
const objEncryptedPackage = result[1].body.message.encrypted_package; | |
const decryptedPackage = decryptPackage(objEncryptedPackage); | |
console.log('Decrypted package', decryptedPackage); | |
} catch (e) { | |
console.log('Decrypt error', e); | |
} | |
break; | |
} | |
default: | |
} | |
} | |
}); | |
function deriveSharedSecret(ecdh, peerB64Pubkey) { | |
const sharedSecretSrc = ecdh.computeSecret(peerB64Pubkey, 'base64'); | |
return crypto.createHash('sha256').update(sharedSecretSrc).digest().slice(0, 16); | |
} | |
function createEncryptedPackage(json, recipientDevicePubkey) { | |
const text = JSON.stringify(json); | |
const ecdh = crypto.createECDH('secp256k1'); | |
const senderEphemeralPubkey = ecdh.generateKeys('base64', 'compressed'); | |
const sharedSecret = deriveSharedSecret(ecdh, recipientDevicePubkey); | |
const iv = crypto.randomBytes(12); | |
const cipher = crypto.createCipheriv('aes-128-gcm', sharedSecret, iv); | |
// under browserify, encryption of long strings fails with Array buffer allocation errors, have to split the string into chunks | |
let arrChunks = []; | |
const CHUNK_LENGTH = 2003; | |
for (let offset = 0; offset < text.length; offset += CHUNK_LENGTH){ | |
// console.log('offset '+offset); | |
arrChunks.push(cipher.update(text.slice(offset, Math.min(offset+CHUNK_LENGTH, text.length)), 'utf8')); | |
} | |
arrChunks.push(cipher.final()); | |
const encryptedMessageBuf = Buffer.concat(arrChunks); | |
arrChunks = null; | |
// const encryptedMessageBuf = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]); | |
// console.log(encryptedMessageBuf); | |
const encryptedMessage = encryptedMessageBuf.toString('base64'); | |
// console.log(encryptedMessage); | |
const authtag = cipher.getAuthTag(); | |
// this is visible and verifiable by the hub | |
return { | |
encrypted_message: encryptedMessage, | |
iv: iv.toString('base64'), | |
authtag: authtag.toString('base64'), | |
dh: { | |
sender_ephemeral_pubkey: senderEphemeralPubkey, | |
recipient_ephemeral_pubkey: recipientDevicePubkey, | |
}, | |
}; | |
} | |
const sendMessageToDevice = async (subject, body) => { | |
const myDeviceHub = 'byteball.org/bb'; | |
const json = { | |
from: myDeviceAddress, // presence of this field guarantees that you cannot strip off the signature and add your own signature instead | |
device_hub: myDeviceHub, | |
subject, | |
body, | |
}; | |
const objTempPubkey = await client.api.getTempPubkey(recipientDevicePubkey); | |
const objEncryptedPackage = createEncryptedPackage(json, objTempPubkey.temp_pubkey); | |
const recipientDeviceAddress = objectHash.getDeviceAddress(recipientDevicePubkey); | |
const objDeviceMessage = { | |
encrypted_package: objEncryptedPackage, | |
to: recipientDeviceAddress, | |
pubkey: objMyPermanentDeviceKey.pub_b64, // who signs. Essentially, the from again. | |
}; | |
objDeviceMessage.signature = sign( | |
objectHash.getDeviceMessageHashToSign(objDeviceMessage), | |
objMyPermanentDeviceKey.priv, | |
); | |
return client.api.deliver(objDeviceMessage); | |
}; | |
function decryptPackage(objEncryptedPackage){ | |
console.log('Expected ' + objEncryptedPackage.dh.recipient_ephemeral_pubkey); | |
const priv_key = objMyTempDeviceKey.priv; | |
if (objMyTempDeviceKey.use_count) | |
objMyTempDeviceKey.use_count++; | |
else | |
objMyTempDeviceKey.use_count = 1; | |
console.log("message encrypted to temp key"); | |
const ecdh = crypto.createECDH('secp256k1'); | |
if (process.browser) // workaround bug in crypto-browserify https://github.com/crypto-browserify/createECDH/issues/9 | |
ecdh.generateKeys("base64", "compressed"); | |
ecdh.setPrivateKey(priv_key); | |
const shared_secret = deriveSharedSecret(ecdh, objEncryptedPackage.dh.sender_ephemeral_pubkey); | |
const iv = Buffer.from(objEncryptedPackage.iv, 'base64'); | |
const decipher = crypto.createDecipheriv('aes-128-gcm', shared_secret, iv); | |
const authtag = Buffer.from(objEncryptedPackage.authtag, 'base64'); | |
decipher.setAuthTag(authtag); | |
const enc_buf = Buffer.from(objEncryptedPackage.encrypted_message, 'base64'); | |
// var decrypted1 = decipher.update(enc_buf); | |
// under browserify, decryption of long buffers fails with Array buffer allocation errors, have to split the buffer into chunks | |
let arrChunks = []; | |
const CHUNK_LENGTH = 4096; | |
for (let offset = 0; offset < enc_buf.length; offset += CHUNK_LENGTH){ | |
// console.log('offset '+offset); | |
arrChunks.push(decipher.update(enc_buf.slice(offset, Math.min(offset+CHUNK_LENGTH, enc_buf.length)))); | |
} | |
const decrypted1 = Buffer.concat(arrChunks); | |
arrChunks = null; | |
let decrypted2; | |
try { | |
decrypted2 = decipher.final(); | |
} catch(e) { | |
return console.log("Failed to decrypt package: " + e); | |
} | |
const decrypted_message_buf = Buffer.concat([decrypted1, decrypted2]); | |
const decrypted_message = decrypted_message_buf.toString("utf8"); | |
console.log("decrypted: "+decrypted_message); | |
const json = JSON.parse(decrypted_message); | |
if (json.encrypted_package){ // strip another layer of encryption | |
console.log("inner encryption"); | |
return decryptPackage(json.encrypted_package); | |
} | |
else | |
return json; | |
} | |
sendMessageToDevice('text', 'Hello world!') | |
.then(result => console.log('Result', result)) | |
.catch(e => console.log('Error', e)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment