import binascii import varint class ExtraParser(object): TX_EXTRA_TAG_PADDING = 0x00 TX_EXTRA_TAG_PUBKEY = 0x01 TX_EXTRA_TAG_EXTRA_NONCE = 0x02 TX_EXTRA_TAG_ADDITIONAL_PUBKEYS = 0x04 KNOWN_TAGS = ( TX_EXTRA_TAG_PADDING, TX_EXTRA_TAG_PUBKEY, TX_EXTRA_TAG_EXTRA_NONCE, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS, ) def __init__(self, extra): if isinstance(extra, str): extra = binascii.unhexlify(extra) if isinstance(extra, bytes): extra = list(extra) self.extra = extra def parse(self): self.data = {} self.offset = 0 self._parse(self.extra) return self.data def _strip_padding(self, extra): while extra and extra[0] == self.TX_EXTRA_TAG_PADDING: extra = extra[1:] self.offset += 1 return extra def _pop_pubkey(self, extra): key = bytes(bytearray(extra[:32])) # bytearray() is for py2 compatibility if len(key) < 32: raise ValueError( "offset {:d}: only {:d} bytes of key data, expected 32".format( self.offset, len(key) ) ) if "pubkeys" in self.data: self.data["pubkeys"].append(key) else: self.data["pubkeys"] = [key] extra = extra[32:] self.offset += 32 return extra def _extract_pubkey(self, extra): if extra: if extra[0] == self.TX_EXTRA_TAG_PUBKEY: extra = extra[1:] self.offset += 1 extra = self._pop_pubkey(extra) elif extra[0] == self.TX_EXTRA_TAG_ADDITIONAL_PUBKEYS: extra = extra[1:] self.offset += 1 keycount = varint.decode_bytes(bytearray(extra)) valen = len(varint.encode(keycount)) extra = extra[valen:] self.offset += valen for i in range(keycount): extra = self._pop_pubkey(extra) return extra def _extract_nonce(self, extra): if extra and extra[0] == self.TX_EXTRA_TAG_EXTRA_NONCE: noncelen = extra[1] extra = extra[2:] self.offset += 2 if noncelen > len(extra): raise ValueError( "offset {:d}: extra nonce exceeds field size".format(self.offset) ) nonce = bytearray(extra[:noncelen]) if "nonces" in self.data: self.data["nonces"].append(nonce) else: self.data["nonces"] = [nonce] extra = extra[noncelen:] self.offset += noncelen return extra def _parse(self, extra): while extra: if extra[0] not in self.KNOWN_TAGS: raise ValueError( "offset {:d}: unknown tag 0x{:x}".format(self.offset, extra[0]) ) extra = self._strip_padding(extra) extra = self._extract_pubkey(extra) extra = self._extract_nonce(extra)