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) ))