mirror of
https://git.wownero.com/lza_menace/wownero-python.git
synced 2024-08-15 03:25:25 +00:00
Add offline subaddress generation
This commit is contained in:
parent
6c1f667840
commit
d0a2d35176
11 changed files with 409 additions and 32 deletions
|
@ -1,4 +1,3 @@
|
|||
import warnings
|
||||
from . import prio
|
||||
from .transaction import PaymentManager
|
||||
|
||||
|
@ -16,6 +15,7 @@ class Account(object):
|
|||
:param index: the account's index within the wallet
|
||||
"""
|
||||
index = None
|
||||
wallet = None
|
||||
|
||||
def __init__(self, backend, index):
|
||||
self.index = index
|
||||
|
|
72
monero/backends/offline.py
Normal file
72
monero/backends/offline.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
from .. import exceptions
|
||||
from ..account import Account
|
||||
from ..address import Address
|
||||
from ..seed import Seed
|
||||
|
||||
|
||||
class WalletIsOffline(exceptions.BackendException):
|
||||
pass
|
||||
|
||||
|
||||
class OfflineWallet(object):
|
||||
"""
|
||||
Offline backend for Monero wallet. Provides support for address generation.
|
||||
"""
|
||||
_address = None
|
||||
_svk = None
|
||||
_ssk = None
|
||||
|
||||
def __init__(self, address, view_key=None, spend_key=None):
|
||||
self._address = Address(address)
|
||||
self._svk = view_key or self._svk
|
||||
self._ssk = spend_key or self._ssk
|
||||
|
||||
def height(self):
|
||||
raise WalletIsOffline()
|
||||
|
||||
def spend_key(self):
|
||||
return self._ssk
|
||||
|
||||
def view_key(self):
|
||||
return self._svk
|
||||
|
||||
def seed(self):
|
||||
return Seed(self._ssk)
|
||||
|
||||
def accounts(self):
|
||||
return [Account(self, 0)]
|
||||
|
||||
def new_account(self, label=None):
|
||||
raise WalletIsOffline()
|
||||
|
||||
def addresses(self, account=0):
|
||||
if account == 0:
|
||||
return [self._address]
|
||||
raise WalletIsOffline()
|
||||
|
||||
def new_address(self, account=0, label=None):
|
||||
raise WalletIsOffline()
|
||||
|
||||
def balances(self, account=0):
|
||||
raise WalletIsOffline()
|
||||
|
||||
def transfers_in(self, account, pmtfilter):
|
||||
raise WalletIsOffline()
|
||||
|
||||
def transfers_out(self, account, pmtfilter):
|
||||
raise WalletIsOffline()
|
||||
|
||||
def export_outputs(self):
|
||||
raise WalletIsOffline()
|
||||
|
||||
def import_outputs(self, outputs_hex):
|
||||
raise WalletIsOffline()
|
||||
|
||||
def export_key_images(self):
|
||||
raise WalletIsOffline()
|
||||
|
||||
def import_key_images(self, key_images):
|
||||
raise WalletIsOffline()
|
||||
|
||||
def transfer(self, *args, **kwargs):
|
||||
raise WalletIsOffline()
|
|
@ -49,6 +49,13 @@ def xrecover(y):
|
|||
if x % 2 != 0: x = q-x
|
||||
return x
|
||||
|
||||
def compress(P):
|
||||
zinv = inv(P[2])
|
||||
return (P[0] * zinv % q, P[1] * zinv % q)
|
||||
|
||||
def decompress(P):
|
||||
return (P[0], P[1], 1, P[0]*P[1] % q)
|
||||
|
||||
By = 4 * inv(5)
|
||||
Bx = xrecover(By)
|
||||
B = [Bx%q, By%q]
|
||||
|
@ -62,6 +69,20 @@ def edwards(P, Q):
|
|||
y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2)
|
||||
return [x3%q, y3%q]
|
||||
|
||||
def add(P, Q):
|
||||
A = (P[1]-P[0])*(Q[1]-Q[0]) % q
|
||||
B = (P[1]+P[0])*(Q[1]+Q[0]) % q
|
||||
C = 2 * P[3] * Q[3] * d % q
|
||||
D = 2 * P[2] * Q[2] % q
|
||||
E = B-A
|
||||
F = D-C
|
||||
G = D+C
|
||||
H = B+A
|
||||
return (E*F, G*H, F*G, E*H)
|
||||
|
||||
def add_compressed(P, Q):
|
||||
return compress(add(decompress(P), decompress(Q)))
|
||||
|
||||
def scalarmult(P, e):
|
||||
if e == 0: return [0, 1]
|
||||
Q = scalarmult(P, e//2)
|
||||
|
@ -92,14 +113,6 @@ def Hint(m):
|
|||
h = H(m)
|
||||
return sum(2**i * bit(h, i) for i in range(2*b))
|
||||
|
||||
def signature(m, sk, pk):
|
||||
h = H(sk)
|
||||
a = 2**(b-2) + sum(2**i * bit(h, i) for i in range(3, b-2))
|
||||
r = Hint(intlist2bytes([indexbytes(h, j) for j in range(b//8, b//4)]) + m)
|
||||
R = scalarmult(B, r)
|
||||
S = (r + Hint(encodepoint(R)+pk+m) * a) % l
|
||||
return encodepoint(R) + encodeint(S)
|
||||
|
||||
def isoncurve(P):
|
||||
x = P[0]
|
||||
y = P[1]
|
||||
|
@ -116,28 +129,29 @@ def decodepoint(s):
|
|||
if not isoncurve(P): raise Exception("decoding point that is not on curve")
|
||||
return P
|
||||
|
||||
def checkvalid(s, m, pk):
|
||||
if len(s) != b//4: raise Exception("signature length is wrong")
|
||||
if len(pk) != b//8: raise Exception("public-key length is wrong")
|
||||
R = decodepoint(s[0:b//8])
|
||||
A = decodepoint(pk)
|
||||
S = decodeint(s[b//8:b//4])
|
||||
h = Hint(encodepoint(R) + pk + m)
|
||||
if scalarmult(B, S) != edwards(R, scalarmult(A, h)):
|
||||
raise Exception("signature does not pass verification")
|
||||
|
||||
# This is from https://github.com/monero-project/mininero by Shen Noether with The Monero Project
|
||||
def scalarmultbase(e):
|
||||
if e == 0: return [0, 1]
|
||||
Q = scalarmult(B, e//2)
|
||||
Q = edwards(Q, Q)
|
||||
if e & 1: Q = edwards(Q, B)
|
||||
return Q
|
||||
# These are unused but let's keep them
|
||||
#def signature(m, sk, pk):
|
||||
# h = H(sk)
|
||||
# a = 2**(b-2) + sum(2**i * bit(h, i) for i in range(3, b-2))
|
||||
# r = Hint(intlist2bytes([indexbytes(h, j) for j in range(b//8, b//4)]) + m)
|
||||
# R = scalarmult(B, r)
|
||||
# S = (r + Hint(encodepoint(R)+pk+m) * a) % l
|
||||
# return encodepoint(R) + encodeint(S)
|
||||
#
|
||||
#def checkvalid(s, m, pk):
|
||||
# if len(s) != b//4: raise Exception("signature length is wrong")
|
||||
# if len(pk) != b//8: raise Exception("public-key length is wrong")
|
||||
# R = decodepoint(s[0:b//8])
|
||||
# A = decodepoint(pk)
|
||||
# S = decodeint(s[b//8:b//4])
|
||||
# h = Hint(encodepoint(R) + pk + m)
|
||||
# if scalarmult(B, S) != edwards(R, scalarmult(A, h)):
|
||||
# raise Exception("signature does not pass verification")
|
||||
|
||||
def public_from_secret(k):
|
||||
keyInt = decodeint(k)
|
||||
aG = scalarmultbase(keyInt)
|
||||
return encodepoint(aG)
|
||||
aB = scalarmult(B, keyInt)
|
||||
return encodepoint(aB)
|
||||
|
||||
def public_from_secret_hex(hk):
|
||||
return hexlify(public_from_secret(unhexlify(hk))).decode()
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
from binascii import hexlify, unhexlify
|
||||
from sha3 import keccak_256
|
||||
import struct
|
||||
|
||||
from . import address
|
||||
from . import base58
|
||||
from . import ed25519
|
||||
from . import prio
|
||||
from .transaction import Payment, PaymentManager
|
||||
|
||||
|
@ -36,6 +43,7 @@ class Wallet(object):
|
|||
self.accounts = self.accounts or []
|
||||
idx = 0
|
||||
for _acc in self._backend.accounts():
|
||||
_acc.wallet = self
|
||||
try:
|
||||
if self.accounts[idx]:
|
||||
continue
|
||||
|
@ -184,6 +192,38 @@ class Wallet(object):
|
|||
"""
|
||||
return self.accounts[0].new_address(label=label)
|
||||
|
||||
def get_address(self, major, minor):
|
||||
"""
|
||||
Calculates sub-address for account index (`major`) and address index within
|
||||
the account (`minor`).
|
||||
|
||||
:rtype: :class:`BaseAddress <monero.address.BaseAddress>`
|
||||
"""
|
||||
master_address = self.address()
|
||||
if major == minor == 0:
|
||||
return master_address
|
||||
master_svk = unhexlify(self.view_key())
|
||||
master_psk = unhexlify(self.address().spend_key())
|
||||
# m = Hs("SubAddr\0" || master_svk || major || minor)
|
||||
hsdata = b''.join([
|
||||
b'SubAddr\0', master_svk,
|
||||
struct.pack('<I', major), struct.pack('<I', minor)])
|
||||
m = keccak_256(hsdata).digest()
|
||||
# TODO: OK, the hash is calculated correctly. What's missing here is ed25519 math
|
||||
# to do the following:
|
||||
# D = master_psk + m * B
|
||||
D = ed25519.add_compressed(
|
||||
ed25519.decodepoint(master_psk),
|
||||
ed25519.scalarmult(ed25519.B, ed25519.decodeint(m)))
|
||||
# C = master_svk * D
|
||||
C = ed25519.scalarmult(D, ed25519.decodeint(master_svk))
|
||||
netbyte = bytearray([
|
||||
42 if master_address.is_mainnet() else \
|
||||
63 if master_address.is_testnet() else 36])
|
||||
data = netbyte + ed25519.encodepoint(D) + ed25519.encodepoint(C)
|
||||
checksum = keccak_256(data).digest()[:4]
|
||||
return address.SubAddress(base58.encode(hexlify(data + checksum)))
|
||||
|
||||
def transfer(self, address, amount,
|
||||
priority=prio.NORMAL, payment_id=None, unlock_time=0,
|
||||
relay=True):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue