diff --git a/assemble_msg.py b/assemble_msg.py new file mode 100644 index 0000000..aad7c9f --- /dev/null +++ b/assemble_msg.py @@ -0,0 +1,221 @@ +import hashlib +import logging +import random +import time +from binascii import hexlify, unhexlify +from struct import pack + +from pybitmessage import ( + defaults, helper_msgcoding, highlevelcrypto, protocol, shared) +from pybitmessage.addresses import ( + decodeAddress, decodeVarint, encodeAddress, encodeVarint) +from pybitmessage.fallback import RIPEMD160Hash +from pybitmessage.helper_ackPayload import genAckPayload + + +logger = logging.getLogger('default') + + +class Address(object): + version = 4 + stream = 1 + sigkey = None + enkey = None + ripe = None + bitfield = pack('>I', 0) + avg_pow = defaults.networkDefaultProofOfWorkNonceTrialsPerByte + avg_extra_bytes = defaults.networkDefaultPayloadLengthExtraBytes + + @classmethod + def fromData(cls, data, addr=None): + # parse addr_data + # SELECT transmitdata FROM pubkeys WHERE address = ? + version, _ = decodeVarint(data[0]) + pos = 1 + stream, length = decodeVarint(data[pos:pos + 10]) + pos += length + bitfield = data[pos:pos + 4] + # TODO: mobile + pos += 4 + sigkey = data[pos:pos + 64] + pos += 64 + enkey = data[pos:pos + 64] + pos += 64 + + if version >= 3: + avg_pow = decodeVarint(data[pos:pos + 10]) + pos += length + extra_bytes, length = decodeVarint(data[pos:pos + 10]) + avg_pow = max( + defaults.networkDefaultProofOfWorkNonceTrialsPerByte, avg_pow) + extra_bytes = max( + defaults.networkDefaultPayloadLengthExtraBytes, extra_bytes) + else: + avg_pow = None + extra_bytes = None + return cls( + version, stream, sigkey, enkey, + addr=addr, avg_pow=avg_pow, extra_bytes=extra_bytes, + bitfield=bitfield) + + def __init__( + self, version, stream, sigkey, enkey, addr=None, ripe=None, + avg_pow=None, extra_bytes=None, bitfield=None + ): + self.version = version + self.stream = stream + self.sigkey = sigkey + self.enkey = enkey + if bitfield: + self.bitfield = bitfield + if ripe is None: + if addr: + status, dversion, dstream, ripe = decodeAddress(addr) + assert status == 'success' + assert version == dversion + assert stream == dstream + else: + _sha = hashlib.new('sha512') + _sha.update(sigkey + enkey) + ripe = RIPEMD160Hash(_sha.digest()).digest() + self.ripe = ripe + if avg_pow: + self.avg_pow = avg_pow + if extra_bytes: + self.extra_bytes = extra_bytes + self.addr = addr or encodeAddress(self.version, self.stream, self.ripe) + + def encrypt(self, data): + return highlevelcrypto.encrypt(data, b'04' + hexlify(self.enkey)) + + +class Identity(Address): + priv_sigkey = None + priv_enkey = None + + @classmethod + def fromWIF(cls, sigkey, enkey, bitfield=pack('>I', 0)): + return cls( + hexlify(shared.decodeWalletImportFormat(sigkey)), + hexlify(shared.decodeWalletImportFormat(enkey)), bitfield=bitfield) + + @classmethod + def fromConfig(cls, addr, config): + sigkey_base58 = config.get(addr, 'privsigningkey') + enkey_base58 = config.get(addr, 'privencryptionkey') + bitfield = 0 + # send ack + if not config.safeGetBoolean(addr, 'dontsendack'): + bitfield |= protocol.BITFIELD_DOESACK + return cls.fromWIF( + sigkey_base58, enkey_base58, bitfield=pack('>I', bitfield)) + + def __init__( + self, sigkey, enkey, addr=None, bitfield=None, + version=None, stream=None, ripe=None, + avg_pow=None, extra_bytes=None + ): + self.priv_sigkey = sigkey + self.priv_enkey = enkey + if version: + self.version = version + if stream: + self.stream = stream + sigkey = unhexlify(highlevelcrypto.privToPub(sigkey))[1:] + enkey = unhexlify(highlevelcrypto.privToPub(enkey))[1:] + super(Identity, self).__init__( + self.version, self.stream, sigkey, enkey, + addr, ripe, avg_pow, extra_bytes) + + def sign(self, data): + return highlevelcrypto.sign(data, self.priv_sigkey) + + def decrypt(self): + pass + + +def assemble_msg( + fromid, toaddr, subject, message, + friend=False, ttl=86400, encoding=2 +): + + if ttl > 28 * 24 * 60 * 60: + ttl = 28 * 24 * 60 * 60 + # add some randomness to the TTL + ttl = int(ttl + random.randrange(-300, 300)) + embedded_time = int(time.time() + ttl) + + payload = encodeVarint(fromid.version) + payload += encodeVarint(fromid.stream) + payload += fromid.bitfield + + payload += fromid.sigkey + fromid.enkey + + if fromid.version >= 3: + payload += encodeVarint( + defaults.networkDefaultProofOfWorkNonceTrialsPerByte if friend + else fromid.avg_pow) + payload += encodeVarint( + defaults.networkDefaultPayloadLengthExtraBytes if friend + else fromid.extra_bytes) + + payload += toaddr.ripe + payload += encodeVarint(encoding) + encoded_message = helper_msgcoding.MsgEncode( + {"subject": subject, "body": message}, encoding) + payload += encodeVarint(encoded_message.length) + payload += encoded_message.data + if not protocol.checkBitfield( + toaddr.bitfield, protocol.BITFIELD_DOESACK + ): + logger.info( + 'Not bothering to include ackdata because' + ' the receiver said that they won\'t relay it anyway.' + ) + ack_payload = '' + else: + if ttl < 24 * 60 * 60: + ttl = 24 * 60 * 60 # 1 day + elif ttl < 7 * 24 * 60 * 60: + ttl = 7 * 24 * 60 * 60 # 1 week + else: + ttl = 28 * 24 * 60 * 60 # 4 weeks + ttl = int(ttl + random.randrange(-300, 300)) + ack_payload = pack('>Q', int(time.time() + ttl)) + \ + genAckPayload(toaddr.stream) + + ack_payload = protocol.CreatePacket('object', ack_payload) + + payload += encodeVarint(len(ack_payload)) + payload += ack_payload + + logger.info('Payload: %s', hexlify(payload)) + + header_tmp = pack('>Q', embedded_time) + '\x00\x00\x00\x02' + \ + encodeVarint(1) + encodeVarint(toaddr.stream) + + signature = fromid.sign(header_tmp + payload) + payload += encodeVarint(len(signature)) + payload += signature + + encrypted = toaddr.encrypt(payload) + + encrypted_payload = header_tmp + encrypted + + return encrypted_payload + + +if __name__ == "__main__": + fromid = Identity( + '93d0b61371a54b53df143b954035d612f8efa8a3ed1cf842c2186bfd8f876665', + '4b0b73a54e19b059dc274ab69df095fe699f43b17397bca26fdf40f4d7400a3a', + 'BM-onkVu1KKL2UaUss5Upg9vXmqd3esTmV79', version=2) + # TODO: generate -> [chan] test + toaddr = Identity.fromWIF( + '5JY1CFeeyN4eyfL35guWAuUqu5VLmd7LojtkNP6wmt5msZxxZ57', + '5J1oDgZDicNhUgbfzBDQqi2m5jUPnDrfZinnTqEEEaLv63jVFTM') + print(hexlify( + assemble_msg( + fromid, toaddr, + 'hello', 'The quick brown fox jumps over the lazy dog.', True) + ))