Skip to content

Instantly share code, notes, and snippets.

@bonustrack
Last active October 22, 2019 13:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bonustrack/379d6e46f2f2d5b92440b02db4e0f6a3 to your computer and use it in GitHub Desktop.
Save bonustrack/379d6e46f2f2d5b92440b02db4e0f6a3 to your computer and use it in GitHub Desktop.
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