1from stem.util import ed25519
2
3from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey, Ed25519PrivateKey
4from cryptography.hazmat.primitives import serialization
5
6from onionbalance.hs_v3.ext import ed25519_exts_ref
7from onionbalance.hs_v3.ext import slow_ed25519
8
9def load_tor_key_from_disk(key_bytes):
10    """
11    Load a private identity key from little-t-tor.
12    """
13    # Verify header
14    if (key_bytes[:29] != b'== ed25519v1-secret: type0 =='):
15        raise ValueError("Tor key does not start with Tor header")
16
17    expanded_sk = key_bytes[32:]
18
19    # The rest should be 64 bytes (a,h):
20    # 32 bytes for secret scalar 'a'
21    # 32 bytes for PRF key 'h'
22    if (len(expanded_sk) != 64):
23        raise ValueError("Tor private key has the wrong length")
24
25    return TorEd25519PrivateKey(expanded_sk)
26
27def _blinded_sign_with_tor_key(msg, identity_key, blinded_key, blinding_nonce):
28    """
29    This is identical to stem's hidden_service.py:_blinded_sign() but takes an
30    extended private key (i.e. in tor format) as its argument, instead of the
31    standard format that hazmat does. It basically omits the "extended the key"
32    step and does everything else the same.
33    """
34    identity_key_bytes = identity_key.private_bytes(
35        encoding = serialization.Encoding.Raw,
36        format = serialization.PrivateFormat.Raw,
37        encryption_algorithm = serialization.NoEncryption(),
38    )
39
40    # blind the ESK with this nonce
41    esk = identity_key_bytes
42
43    mult = 2 ** (ed25519.b - 2) + sum(2 ** i * ed25519.bit(blinding_nonce, i) for i in range(3, ed25519.b - 2))
44    s = ed25519.decodeint(esk[:32])
45    s_prime = (s * mult) % ed25519.l
46    k = esk[32:]
47    k_prime = ed25519.H(b'Derive temporary signing key hash input' + k)[:32]
48    blinded_esk = ed25519.encodeint(s_prime) + k_prime
49
50    # finally, sign the message
51
52    a = ed25519.decodeint(blinded_esk[:32])
53    r = ed25519.Hint(b''.join([blinded_esk[i:i + 1] for i in range(ed25519.b // 8, ed25519.b // 4)]) + msg)
54    R = ed25519.scalarmult(ed25519.B, r)
55    S = (r + ed25519.Hint(ed25519.encodepoint(R) + blinded_key + msg) * a) % ed25519.l
56
57    return ed25519.encodepoint(R) + ed25519.encodeint(S)
58
59"""
60Tor ed25519 keys
61
62Expose classes for ed25519 keys in Tor's extended key format which can mimic
63the hazmat ed25519 public/private key classes, so that we can use them
64interchangeably in stem.
65
66Tor uses the "extended" (a,h) format for its private keys, whereas hazmat uses
67the "standard" (seed,A) format: https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/
68
69Since you can't go from the "extended" format to the "standard" format, we
70created these wrappers that act exactly like hazmat keys when it comes to their
71interface.
72"""
73
74
75class TorEd25519PrivateKey(object):
76    """
77    Represents the private part of a blinded ed25519 key of an onion service
78    and should expose a public_key() method and a sign() method.
79    """
80    def __init__(self, expanded_sk):
81        self.priv_key = expanded_sk
82        self.pub_key_bytes = ed25519_exts_ref.publickeyFromESK(self.priv_key)
83        self.pub_key = TorEd25519PublicKey(self.pub_key_bytes)
84
85    def public_key(self):
86        return self.pub_key
87
88    def private_bytes(self, encoding=None, format=None, encryption_algorithm=None):
89        return self.priv_key
90
91    def sign(self, msg):
92        return ed25519_exts_ref.signatureWithESK(msg, self.priv_key, self.pub_key_bytes)
93
94    @property
95    def __class__(self):
96        """
97        This is an epic hack to make this class look like a hazmat ed25519 public
98        key in the eyes of stem:
99          https://github.com/asn-d6/onionbalance/issues/10#issuecomment-610425916
100
101        The __class_ attribute is what's being used by unittest.mock and the C
102        API to trick isinstance() checks, so as long as stem uses isinstance()
103        this is gonna work:
104          https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.__class__
105          https://docs.python.org/3/c-api/object.html#c.PyObject_IsInstance
106        """
107        return Ed25519PrivateKey
108
109class TorEd25519PublicKey(object):
110    """
111    Represents the public blinded ed25519 key of an onion service and should
112    expose a public_bytes() method and a verify() method.
113    """
114    def __init__(self, public_key):
115        self.public_key = public_key
116
117    def public_bytes(self, encoding=None, format=None):
118        return self.public_key
119
120    def verify(self, signature, message):
121        """
122        raises exception if sig not valid
123        """
124        slow_ed25519.checkvalid(signature, message, self.public_key)
125
126    @property
127    def __class__(self):
128        return Ed25519PublicKey
129