Browse Source

added initial files

master
jamescoxon 2 months ago
parent
commit
faad20540d
37 changed files with 4208 additions and 0 deletions
  1. +54
    -0
      .gitignore
  2. +1
    -0
      jcnanolib/README.md
  3. +0
    -0
      jcnanolib/__init__.py
  4. +386
    -0
      jcnanolib/nano.py
  5. +22
    -0
      nano25519/LICENSE
  6. +67
    -0
      nano25519/README.md
  7. +272
    -0
      nano25519/misc/djbec.py
  8. +107
    -0
      nano25519/misc/orig_ed25519.py
  9. +40
    -0
      nano25519/misc/speed_orig_ed25519.py
  10. +0
    -0
      nano25519/nano25519/__init__.py
  11. +66
    -0
      nano25519/nano25519/_ed25519.py
  12. +348
    -0
      nano25519/nano25519/basic.py
  13. +16
    -0
      nano25519/nano25519/dh.py
  14. +45
    -0
      nano25519/nano25519/do_ed25519_kat.py
  15. +191
    -0
      nano25519/nano25519/ed25519_oop.py
  16. +73
    -0
      nano25519/nano25519/eddsa.py
  17. +1025
    -0
      nano25519/nano25519/kat-ed25519.txt
  18. +47
    -0
      nano25519/nano25519/slow_basic.py
  19. +48
    -0
      nano25519/nano25519/spake2.py
  20. +94
    -0
      nano25519/nano25519/speed_basic.py
  21. +39
    -0
      nano25519/nano25519/speed_dh.py
  22. +47
    -0
      nano25519/nano25519/speed_ed25519.py
  23. +39
    -0
      nano25519/nano25519/speed_spake2.py
  24. +157
    -0
      nano25519/nano25519/test_basic.py
  25. +18
    -0
      nano25519/nano25519/test_dh.py
  26. +263
    -0
      nano25519/nano25519/test_ed25519.py
  27. +60
    -0
      nano25519/nano25519/test_orders.py
  28. +185
    -0
      nano25519/nano25519/test_replacements.py
  29. +29
    -0
      nano25519/nano25519/test_spake2.py
  30. +48
    -0
      nano25519/setup.py
  31. +115
    -0
      read_log.py
  32. +138
    -0
      run.py
  33. BIN
      static/css/images/header.jpg
  34. +13
    -0
      templates/alreadyreg.html
  35. +119
    -0
      templates/base.html
  36. +20
    -0
      templates/homepage.html
  37. +16
    -0
      templates/result.html

+ 54
- 0
.gitignore View File

@@ -0,0 +1,54 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml

# Translations
*.mo
*.pot

# Django stuff:
*.log

# Sphinx documentation
docs/_build/

# PyBuilder
target/

+ 1
- 0
jcnanolib/README.md View File

@@ -0,0 +1 @@
# jcnanolib

+ 0
- 0
jcnanolib/__init__.py View File


+ 386
- 0
jcnanolib/nano.py View File

@@ -0,0 +1,386 @@
import time, json, random

import binascii
from bitstring import BitArray
from hashlib import blake2b
#from pyblake2 import blake2b
from nano25519 import ed25519_oop as ed25519
import requests

representative = 'nano_1kd4h9nqaxengni43xy9775gcag8ptw8ddjifnm77qes1efuoqikoqy5sjq3'
rai_node_address = 'http://%s:%s' % ("127.0.0.1", "7076")
url_address = rai_node_address

time_out = 3

def private_public(private):
return ed25519.SigningKey(private).get_verifying_key().to_bytes()


def xrb_account(address):
# Given a string containing an XRB address, confirm validity and
# provide resulting hex address
print(address)
if len(address) == 65 and (address[:5] == 'nano_'):
# each index = binary value, account_lookup[0] == '1'
account_map = "13456789abcdefghijkmnopqrstuwxyz"
account_lookup = {}
# populate lookup index with prebuilt bitarrays ready to append
for i in range(32):
account_lookup[account_map[i]] = BitArray(uint=i ,length=5)

# we want everything after 'xrb_' but before the 8-char checksum
acrop_key = address[5:-8]
# extract checksum
acrop_check = address[-8:]

# convert base-32 (5-bit) values to byte string by appending each
# 5-bit value to the bitstring, essentially bitshifting << 5 and
# then adding the 5-bit value.
number_l = BitArray()
for x in range(0, len(acrop_key)):
number_l.append(account_lookup[acrop_key[x]])
# reduce from 260 to 256 bit (upper 4 bits are never used as account
# is a uint256)
number_l = number_l[4:]

check_l = BitArray()
for x in range(0, len(acrop_check)):
check_l.append(account_lookup[acrop_check[x]])

# reverse byte order to match hashing format
check_l.byteswap()
result = number_l.hex.upper()

# verify checksum
h = blake2b(digest_size=5)
h.update(number_l.bytes)
if (h.hexdigest() == check_l.hex):
return result
else:
print('error1')
return False
else:
print('error2')
return False


def account_xrb(account):
# Given a string containing a hex address, encode to public address
# format with checksum
# each index = binary value, account_lookup['00001'] == '3'
account_map = "13456789abcdefghijkmnopqrstuwxyz"
account_lookup = {}
# populate lookup index for binary string to base-32 string character
for i in range(32):
account_lookup[BitArray(uint=i ,length=5).bin] = account_map[i]
# hex string > binary
account = BitArray(hex=account)

# get checksum
h = blake2b(digest_size=5)
h.update(account.bytes)
checksum = BitArray(hex=h.hexdigest())

# encode checksum
# swap bytes for compatibility with original implementation
checksum.byteswap()
encode_check = ''
for x in range(0 ,int(len(checksum.bin ) /5)):
# each 5-bit sequence = a base-32 character from account_map
encode_check += account_lookup[checksum.bin[ x *5: x * 5 +5]]

# encode account
encode_account = ''
while len(account.bin) < 260:
# pad our binary value so it is 260 bits long before conversion
# (first value can only be 00000 '1' or 00001 '3')
account = '0b0' + account
for x in range(0 ,int(len(account.bin ) /5)):
# each 5-bit sequence = a base-32 character from account_map
encode_account += account_lookup[account.bin[ x *5: x * 5 +5]]

# build final address string
return 'nano_' +encode_account +encode_check


def seed_account(seed, index):
# Given an account seed and index #, provide the account private and
# public keys
h = blake2b(digest_size=32)

seed_data = BitArray(hex=seed)
seed_index = BitArray(int=index ,length=32)

h.update(seed_data.bytes)
h.update(seed_index.bytes)

account_key = BitArray(h.digest())
return account_key.bytes, private_public(account_key.bytes)


def receive_xrb(index, account, wallet_seed, api_key):
# Get pending blocks

rx_data = get_pending(str(account))
if len(rx_data) == 0:
return

for block in rx_data:
#print(block)
block_hash = block
#print(rx_data[block])
balance = int(rx_data[block]['amount'])
source = rx_data[block]['source']

previous = get_previous(str(account))

current_balance = get_balance(previous)
if current_balance == 'timeout':
return 'timeout'
#print(current_balance)
new_balance = int(current_balance) + int(balance)
hex_balance = hex(new_balance)
#print(hex_balance)
hex_final_balance = hex_balance[2:].upper().rjust(32, '0')
#print(hex_final_balance)

priv_key, pub_key = seed_account(wallet_seed, int(index))
public_key = ed25519.SigningKey(priv_key).get_verifying_key().to_ascii(encoding="hex")

# print("Starting PoW Generation")
work = get_pow(previous, api_key)
if work == 'timeout':
return 'timeout'
# print("Completed PoW Generation")

# Calculate signature
bh = blake2b(digest_size=32)
bh.update(BitArray(hex='0x0000000000000000000000000000000000000000000000000000000000000006').bytes)
bh.update(BitArray(hex=xrb_account(account)).bytes)
bh.update(BitArray(hex=previous).bytes)
bh.update(BitArray(hex=xrb_account(account)).bytes)
bh.update(BitArray(hex=hex_final_balance).bytes)
bh.update(BitArray(hex=block_hash).bytes)

sig = ed25519.SigningKey(priv_key +pub_key).sign(bh.digest())
signature = str(binascii.hexlify(sig), 'ascii')

finished_block = '{ "type" : "state", "previous" : "%s", "representative" : "%s" , "account" : "%s", "balance" : "%s", "link" : "%s", \
"work" : "%s", "signature" : "%s" }' % \
(previous, account, account, new_balance, block_hash, work, signature)

data = requests.post(url_address, json = {"action":"process", "block" : finished_block}, timeout=time_out)
block_reply = data.json()
return block_reply, balance

def rapid_process(block_hash, balance, index, account, wallet_seed, api_key):

previous = get_previous(str(account))

current_balance = get_balance(previous)
if current_balance == 'timeout':
return 'timeout'
#print(current_balance)
new_balance = int(current_balance) + int(balance)
hex_balance = hex(new_balance)
#print(hex_balance)
hex_final_balance = hex_balance[2:].upper().rjust(32, '0')
#print(hex_final_balance)

priv_key, pub_key = seed_account(wallet_seed, int(index))
public_key = ed25519.SigningKey(priv_key).get_verifying_key().to_ascii(encoding="hex")

# print("Starting PoW Generation")
work = get_pow(previous, api_key)
if work == 'timeout':
return 'timeout'
# print("Completed PoW Generation")

# Calculate signature
bh = blake2b(digest_size=32)
bh.update(BitArray(hex='0x0000000000000000000000000000000000000000000000000000000000000006').bytes)
bh.update(BitArray(hex=xrb_account(account)).bytes)
bh.update(BitArray(hex=previous).bytes)
bh.update(BitArray(hex=xrb_account(account)).bytes)
bh.update(BitArray(hex=hex_final_balance).bytes)
bh.update(BitArray(hex=block_hash).bytes)

sig = ed25519.SigningKey(priv_key +pub_key).sign(bh.digest())
signature = str(binascii.hexlify(sig), 'ascii')

finished_block = '{ "type" : "state", "previous" : "%s", "representative" : "%s" , "account" : "%s", "balance" : "%s", "link" : "%s", \
"work" : "%s", "signature" : "%s" }' % \
(previous, account, account, new_balance, block_hash, work, signature)

data = requests.post(url_address, json = {"action":"process", "block" : finished_block}, timeout=time_out)
block_reply = data.json()
return block_reply, balance


def get_address(index, wallet_seed):
# Generate address
print("Generate Address")
priv_key, pub_key = seed_account(wallet_seed, int(index))
public_key = str(binascii.hexlify(pub_key), 'ascii')
print("Public Key: ", str(public_key))

account = account_xrb(str(public_key))
print("Account Address: ", account)
return account

def open_xrb(index, account, wallet_seed, api_key):
# Get pending blocks

rx_data = get_pending(str(account))
for block in rx_data:
#print(block)
block_hash = block
#print(rx_data[block])
balance = int(rx_data[block]['amount'])
source = rx_data[block]['source']

hex_balance = hex(balance)
#print(hex_balance)
hex_final_balance = hex_balance[2:].upper().rjust(32, '0')
#print(hex_final_balance)

priv_key, pub_key = seed_account(wallet_seed, int(index))
public_key = ed25519.SigningKey(priv_key).get_verifying_key().to_ascii(encoding="hex")

# print("Starting PoW Generation")
work = get_pow(str(public_key, 'ascii'), api_key)
# print("Completed PoW Generation")

# Calculate signature
bh = blake2b(digest_size=32)
bh.update(BitArray(hex='0x0000000000000000000000000000000000000000000000000000000000000006').bytes)
bh.update(BitArray(hex=xrb_account(account)).bytes)
bh.update(BitArray(hex='0x0000000000000000000000000000000000000000000000000000000000000000').bytes)
bh.update(BitArray(hex=xrb_account(account)).bytes)
bh.update(BitArray(hex=hex_final_balance).bytes)
bh.update(BitArray(hex=block_hash).bytes)

sig = ed25519.SigningKey(priv_key + pub_key).sign(bh.digest())
signature = str(binascii.hexlify(sig), 'ascii')

finished_block = '{ "type" : "state", "previous" : "0000000000000000000000000000000000000000000000000000000000000000", "representative" : "%s" , "account" : "%s", "balance" : "%s", "link" : "%s", \
"work" : "%s", "signature" : "%s" }' % (account, account, balance, block_hash, work, signature)
data = requests.post(url_address, json = {"action":"process", "block" : finished_block}, timeout=time_out)
block_reply = data.json()
return block_reply, balance


def send_xrb(dest_account, amount, account, index, wallet_seed, api_key):

previous = get_previous(str(account))

current_balance = get_balance(previous)
#print(current_balance)
new_balance = int(current_balance) - int(amount)
hex_balance = hex(new_balance)

#print(hex_balance)
hex_final_balance = hex_balance[2:].upper().rjust(32, '0')
#print(hex_final_balance)

priv_key, pub_key = seed_account(wallet_seed, int(index))
public_key = ed25519.SigningKey(priv_key).get_verifying_key().to_ascii(encoding="hex")

# print("Starting PoW Generation")
work = get_pow(previous, api_key)
# print("Completed PoW Generation")

# Calculate signature
bh = blake2b(digest_size=32)
bh.update(BitArray(hex='0x0000000000000000000000000000000000000000000000000000000000000006').bytes)
bh.update(BitArray(hex=xrb_account(account)).bytes)
bh.update(BitArray(hex=previous).bytes)
bh.update(BitArray(hex=xrb_account(account)).bytes)
bh.update(BitArray(hex=hex_final_balance).bytes)
bh.update(BitArray(hex=xrb_account(dest_account)).bytes)

sig = ed25519.SigningKey(priv_key + pub_key).sign(bh.digest())
signature = str(binascii.hexlify(sig), 'ascii')

finished_block = '{ "type" : "state", "previous" : "%s", "representative" : "%s" , "account" : "%s", "balance" : "%s", "link" : "%s", \
"work" : "%s", "signature" : "%s" }' % (
previous, account, account, new_balance, dest_account, work, signature)

data = requests.post(url_address, json = {"action":"process", "block" : finished_block}, timeout=time_out)
block_reply = data.json()
return block_reply


def get_pow(hash, api_key):
data = requests.post('https://dpow.nanocenter.org/service/', json = {"user": "jayycox", "api_key" : api_key, "hash" : hash}, timeout=10)
#Generate work
resulting_data = data.json()
if 'work' in resulting_data:
work = resulting_data['work']
print(work)
else:
work = 'error pow'
return work


def get_previous(account):
# Get account info
accounts_list = [account]
try:
data = requests.post(url_address, json = {"action":"accounts_frontiers", "accounts" : accounts_list}, timeout=time_out)
except requests.exceptions.Timeout:
print("Error: Timeout")
return "timeout"

account_info = data.json()

if len(account_info['frontiers']) == 0:
return ""
else:
previous = account_info['frontiers'][account]
return previous


def get_balance(hash):
# Get balance from hash
try:
data = requests.post(url_address, json = {"action":"block", "hash" : hash}, timeout=time_out)
except requests.exceptions.Timeout:
print("Error: Timeout")
return "timeout"

rx_data = data.json()
if "error" in rx_data:
return ""
else:
new_rx = json.loads(str(rx_data['contents']))
return new_rx['balance']

def get_account_balance(account):
# Get balance from hash
try:
data = requests.post(url_address, json = {"action":"account_balance", "account" : account}, timeout=time_out)
except requests.exceptions.Timeout:
print("Error: Timeout")
return "timeout"

rx_data = data.json()
#print(rx_data)
if "error" in rx_data:
return ""
else:
#new_rx = json.loads(str(rx_data['contents']))
return rx_data['balance']

def get_pending(account):
try:
data = requests.post(url_address, json = {"action":"pending", "count" : "1", "account" : account, "source" : "true"}, timeout=time_out)
except requests.exceptions.Timeout:
print("Error: Timeout")
return "timeout"

rx_data = data.json()

return rx_data['blocks']

+ 22
- 0
nano25519/LICENSE View File

@@ -0,0 +1,22 @@
"python-pure25519" Copyright (c) 2015 Brian Warner and other contributors

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

+ 67
- 0
nano25519/README.md View File

@@ -0,0 +1,67 @@
# Nano25519

This is a forked version of python-pure25519 from https://github.com/icarusglider/PyRai.

This library uses Blake over SHA hashing functions to be compatible with Nano.

# python-pure25519

This contains a collection of pure-python functions to implement Curve25519-based cryptography, including:

* Diffie-Hellman Key Agreement
* Ed25519 digital signatures
* SPAKE2 Password Authenticated Key Agreement

You almost certainly want to use [pynacl](https://pypi.python.org/pypi/PyNaCl/) or [python-ed25519](https://pypi.python.org/pypi/ed25519) instead, which are python bindings to djb's C implementations of Curve25519/Ed25519 (and the rest of the NaCl suite).

Bad things about this module:

* much slower than C
* not written by djb, so probably horribly buggy and insecure
* very much not constant-time: leaks hamming weights like crazy

Good things about this module:

* can be used without a C compiler
* compatible with python2 and python3
* exposes enough point math (addition and scalarmult) to implement SPAKE2

## Slow

The pure-python functions are considerably slower than their pynacl (libsodium) equivalents, using python-2.7.9 on my 2.6GHz Core-i7:

| function | pure25519 | pynacl (C) |
| -------------- | --------- | ---------- |
| Ed25519 sign | 2.8 ms | 142 us |
| Ed25519 verify | 10.8 ms | 240 us |
| DH-start | 2.8 ms | 72 us |
| DH-finish | 5.4 ms | 89 us |
| SPAKE2 start | 5.4 ms | N/A |
| SPAKE2 finish | 8.0 ms | N/A |

This library is conservative, and performs full subgroup-membership checks on decoded points, which adds considerable overhead. The Curve25519/Ed25519 algorithms were designed to not require these checks, so a careful application might be able to improve on this slightly (Ed25519 verify down to
6.2ms, DH-finish to 3.2ms).

# Compatibility, and the lack thereof

The sample Diffie-Hellman key-agreement code in dh.py is not actually Curve25519: it uses the Ed25519 curve, which is sufficiently similar for security purposes, but won't interoperate with a proper Curve25519 implementation. It is included just to exercise the API and obtain a comparable performance number.

The Ed25519 implementation *should* be compatible with other versions, and includes the known-answer-tests from http://ed25519.cr.yp.to/software.html to confirm this.

The SPAKE2 implementation is new, and there's nothing else for it to interoperate with yet.

## Sources

This code is adapted and modified from a number of original sources,
including:

* https://bitbucket.org/dholth/ed25519ll
* http://ed25519.cr.yp.to/python/ed25519.py
* http://ed25519.cr.yp.to/software.html
* http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html

Many thanks to Ron Garret, Daniel Holth, and Matthew Dempsky.

## License

This software is released under the MIT license.

+ 272
- 0
nano25519/misc/djbec.py View File

@@ -0,0 +1,272 @@
# Ed25519 digital signatures
# Based on http://ed25519.cr.yp.to/python/ed25519.py
# See also http://ed25519.cr.yp.to/software.html
# Adapted by Ron Garret
# Sped up considerably using coordinate transforms found on:
# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
# Specifically add-2008-hwcd-4 and dbl-2008-hwcd

try: # pragma nocover
unicode
PY3 = False
def asbytes(b):
"""Convert array of integers to byte string"""
return ''.join(chr(x) for x in b)
def joinbytes(b):
"""Convert array of bytes to byte string"""
return ''.join(b)
def bit(h, i):
"""Return i'th bit of bytestring h"""
return (ord(h[i//8]) >> (i%8)) & 1

except NameError: # pragma nocover
PY3 = True
asbytes = bytes
joinbytes = bytes
def bit(h, i):
return (h[i//8] >> (i%8)) & 1

import hashlib
from pyblake2 import blake2b

b = 256
q = 2**255 - 19
l = 2**252 + 27742317777372353535851937790883648493

def H(m):
#return hashlib.sha512(m).digest()
return blake2b(m).digest()

def expmod(b, e, m):
if e == 0: return 1
t = expmod(b, e // 2, m) ** 2 % m
if e & 1: t = (t * b) % m
return t

# Can probably get some extra speedup here by replacing this with
# an extended-euclidean, but performance seems OK without that
def inv(x):
return expmod(x, q-2, q)

d = -121665 * inv(121666)
I = expmod(2,(q-1)//4,q)

def xrecover(y):
xx = (y*y-1) * inv(d*y*y+1)
x = expmod(xx,(q+3)//8,q)
if (x*x - xx) % q != 0: x = (x*I) % q
if x % 2 != 0: x = q-x
return x

By = 4 * inv(5)
Bx = xrecover(By)
B = [Bx % q,By % q]

#def edwards(P,Q):
# x1 = P[0]
# y1 = P[1]
# x2 = Q[0]
# y2 = Q[1]
# x3 = (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2)
# y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2)
# return (x3 % q,y3 % q)

#def scalarmult(P,e):
# if e == 0: return [0,1]
# Q = scalarmult(P,e/2)
# Q = edwards(Q,Q)
# if e & 1: Q = edwards(Q,P)
# return Q

# Faster (!) version based on:
# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html

def xpt_add(pt1, pt2):
(X1, Y1, Z1, T1) = pt1
(X2, Y2, Z2, T2) = pt2
A = ((Y1-X1)*(Y2+X2)) % q
B = ((Y1+X1)*(Y2-X2)) % q
C = (Z1*2*T2) % q
D = (T1*2*Z2) % q
E = (D+C) % q
F = (B-A) % q
G = (B+A) % q
H = (D-C) % q
X3 = (E*F) % q
Y3 = (G*H) % q
Z3 = (F*G) % q
T3 = (E*H) % q
return (X3, Y3, Z3, T3)

def xpt_double (pt):
(X1, Y1, Z1, _) = pt
A = (X1*X1)
B = (Y1*Y1)
C = (2*Z1*Z1)
D = (-A) % q
J = (X1+Y1) % q
E = (J*J-A-B) % q
G = (D+B) % q
F = (G-C) % q
H = (D-B) % q
X3 = (E*F) % q
Y3 = (G*H) % q
Z3 = (F*G) % q
T3 = (E*H) % q
return (X3, Y3, Z3, T3)

def pt_xform (pt):
(x, y) = pt
return (x, y, 1, (x*y)%q)

def pt_unxform (pt):
(x, y, z, _) = pt
return ((x*inv(z))%q, (y*inv(z))%q)

def xpt_mult (pt, n):
if n==0: return pt_xform((0,1))
_ = xpt_double(xpt_mult(pt, n>>1))
return xpt_add(_, pt) if n&1 else _

def scalarmult(pt, e):
return pt_unxform(xpt_mult(pt_xform(pt), e))

def encodeint(y):
bits = [(y >> i) & 1 for i in range(b)]
e = [(sum([bits[i * 8 + j] << j for j in range(8)]))
for i in range(b//8)]
return asbytes(e)

def encodepoint(P):
x = P[0]
y = P[1]
bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1]
e = [(sum([bits[i * 8 + j] << j for j in range(8)]))
for i in range(b//8)]
return asbytes(e)
def publickey(sk):
h = H(sk)
a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
A = scalarmult(B,a)
return encodepoint(A)

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))
inter = joinbytes([h[i] for i in range(b//8,b//4)])
r = Hint(inter + 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]
return (-x*x + y*y - 1 - d*x*x*y*y) % q == 0

def decodeint(s):
return sum(2**i * bit(s,i) for i in range(0,b))

def decodepoint(s):
y = sum(2**i * bit(s,i) for i in range(0,b-1))
x = xrecover(y)
if x & 1 != bit(s,b-1): x = q-x
P = [x,y]
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)
v1 = scalarmult(B,S)
# v2 = edwards(R,scalarmult(A,h))
v2 = pt_unxform(xpt_add(pt_xform(R), pt_xform(scalarmult(A, h))))
return v1==v2

##########################################################
#
# Curve25519 reference implementation by Matthew Dempsky, from:
# http://cr.yp.to/highspeed/naclcrypto-20090310.pdf

# P = 2 ** 255 - 19
P = q
A = 486662

#def expmod(b, e, m):
# if e == 0: return 1
# t = expmod(b, e / 2, m) ** 2 % m
# if e & 1: t = (t * b) % m
# return t

# def inv(x): return expmod(x, P - 2, P)

def add(n, m, d):
(xn, zn) = n
(xm, zm) = m
(xd, zd) = d
x = 4 * (xm * xn - zm * zn) ** 2 * zd
z = 4 * (xm * zn - zm * xn) ** 2 * xd
return (x % P, z % P)

def double(n):
(xn, zn) = n
x = (xn ** 2 - zn ** 2) ** 2
z = 4 * xn * zn * (xn ** 2 + A * xn * zn + zn ** 2)
return (x % P, z % P)

def curve25519(n, base=9):
one = (base,1)
two = double(one)
# f(m) evaluates to a tuple
# containing the mth multiple and the
# (m+1)th multiple of base.
def f(m):
if m == 1: return (one, two)
(pm, pm1) = f(m // 2)
if (m & 1):
return (add(pm, pm1, one), double(pm1))
return (double(pm), add(pm, pm1, one))
((x,z), _) = f(n)
return (x * inv(z)) % P

import random

def genkey(n=0):
n = n or random.randint(0,P)
n &= ~7
n &= ~(128 << 8 * 31)
n |= 64 << 8 * 31
return n

#def str2int(s):
# return int(hexlify(s), 16)
# # return sum(ord(s[i]) << (8 * i) for i in range(32))
#
#def int2str(n):
# return unhexlify("%x" % n)
# # return ''.join([chr((n >> (8 * i)) & 255) for i in range(32)])

#################################################

def dsa_test():
import os
msg = str(random.randint(q,q+q)).encode('utf-8')
sk = os.urandom(32)
pk = publickey(sk)
sig = signature(msg, sk, pk)
return checkvalid(sig, msg, pk)

def dh_test():
sk1 = genkey()
sk2 = genkey()
return curve25519(sk1, curve25519(sk2)) == curve25519(sk2, curve25519(sk1))


+ 107
- 0
nano25519/misc/orig_ed25519.py View File

@@ -0,0 +1,107 @@
# from http://ed25519.cr.yp.to/python/ed25519.py
import hashlib
from pyblake2 import blake2b

b = 256
q = 2**255 - 19
l = 2**252 + 27742317777372353535851937790883648493

def H(m):
#return hashlib.sha512(m).digest()
return blake2b(m).digest()

def expmod(b,e,m):
if e == 0: return 1
t = expmod(b,e/2,m)**2 % m
if e & 1: t = (t*b) % m
return t

def inv(x):
return expmod(x,q-2,q)

d = -121665 * inv(121666)
I = expmod(2,(q-1)/4,q)

def xrecover(y):
xx = (y*y-1) * inv(d*y*y+1)
x = expmod(xx,(q+3)/8,q)
if (x*x - xx) % q != 0: x = (x*I) % q
if x % 2 != 0: x = q-x
return x

By = 4 * inv(5)
Bx = xrecover(By)
B = [Bx % q,By % q]

def edwards(P,Q):
x1 = P[0]
y1 = P[1]
x2 = Q[0]
y2 = Q[1]
x3 = (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2)
y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2)
return [x3 % q,y3 % q]

def scalarmult(P,e):
if e == 0: return [0,1]
Q = scalarmult(P,e/2)
Q = edwards(Q,Q)
if e & 1: Q = edwards(Q,P)
return Q

def encodeint(y):
bits = [(y >> i) & 1 for i in range(b)]
return ''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b/8)])

def encodepoint(P):
x = P[0]
y = P[1]
bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1]
return ''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b/8)])

def bit(h,i):
return (ord(h[i/8]) >> (i%8)) & 1

def publickey(sk):
h = H(sk)
a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
A = scalarmult(B,a)
return encodepoint(A)

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(''.join([h[i] for i 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]
return (-x*x + y*y - 1 - d*x*x*y*y) % q == 0

def decodeint(s):
return sum(2**i * bit(s,i) for i in range(0,b))

def decodepoint(s):
y = sum(2**i * bit(s,i) for i in range(0,b-1))
x = xrecover(y)
if x & 1 != bit(s,b-1): x = q-x
P = [x,y]
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")

+ 40
- 0
nano25519/misc/speed_orig_ed25519.py View File

@@ -0,0 +1,40 @@
import timeit

def do(setup_statements, statement):
# extracted from timeit.py
t = timeit.Timer(stmt=statement,
setup="\n".join(setup_statements))
# determine number so that 1.0 <= total time < 10.0
for i in range(1, 10):
number = 10**i
x = t.timeit(number)
if x >= 1.0:
break
return x / number

def abbrev(t):
if t > 1.0:
return "%.3fs" % t
if t > 1e-3:
return "%.2fms" % (t*1e3)
return "%.2fus" % (t*1e6)

def p(name, setup_statements, statements):
t = sorted([do(setup_statements, statements) for i in range(3)])
print("%12s: %s (%s)" % (name,
abbrev(min(t)),
" ".join([abbrev(s) for s in t])))

def run():
S1 = "import orig_ed25519 as orig; from hashlib import sha256; msg=b'hello world'; sk=b'32-ish random bytes'"
S2 = "pk = orig.publickey(sk)"
S3 = "sig = orig.signature(msg,sk,pk)"
S4 = "orig.checkvalid(sig,msg,pk)"

print("speed_orig")
p("generate", [S1], S2)
p("sign", [S1, S2], S3)
p("verify", [S1, S2, S3], S4)

if __name__ == "__main__":
run()

+ 0
- 0
nano25519/nano25519/__init__.py View File


+ 66
- 0
nano25519/nano25519/_ed25519.py View File

@@ -0,0 +1,66 @@
from . import eddsa

# adapt pure25519/ed25519.py to behave like (C/glue) ed25519/_ed25519.py, so
# ed25519_oop.py doesn't have to change

# ed25519 secret/private/signing keys can be built from a 32-byte random seed
# (clamped and treated as a scalar). The public/verifying key is a 32-byte
# encoded group element. Signing requires both, so a common space/time
# tradeoff is to glue the two together and call the 64-byte combination the
# "secret key". Signatures are 64 bytes (one encoded group element and one
# encoded scalar). The NaCl code prefers to glue the signature to the
# message, rather than pass around detacted signatures.
#
# for clarity, we use the following notation:
# seed: 32-byte secret random seed (unclamped)
# sk: = seed
# vk: 32-byte verifying key (encoded group element)
# seed+vk: 64-byte glue thing (sometimes stored as secret key)
# sig: 64-byte detached signature (R+S)
# sig+msg: signature concatenated to message

# that glue provides:
# SECRETKEYBYTES=64, PUBLICKEYBYTES=32, SIGNATUREBYTES=64
# (vk,seed+vk)=publickey(seed)
# sig+msg = sign(msg, seed+vk)
# msg = open(sig+msg, vk) # or raise BadSignatureError

# pure25519/ed25519.py provides:
# vk = publickey(sk)
# sig = signature(msg, sk or sk+vk, vk)
# bool = checkvalid(sig, msg, vk)

class BadSignatureError(Exception):
pass

SECRETKEYBYTES = 64
PUBLICKEYBYTES = 32
SIGNATUREKEYBYTES = 64

def publickey(seed32):
assert len(seed32) == 32
vk32 = eddsa.publickey(seed32)
return vk32, seed32+vk32

def sign(msg, skvk):
assert len(skvk) == 64
sk = skvk[:32]
vk = skvk[32:]
sig = eddsa.signature(msg, sk, vk)
return sig+msg

def open(sigmsg, vk):
assert len(vk) == 32
sig = sigmsg[:64]
msg = sigmsg[64:]
try:
valid = eddsa.checkvalid(sig, msg, vk)
except ValueError as e:
raise BadSignatureError(e)
except Exception as e:
if str(e) == "decoding point that is not on curve":
raise BadSignatureError(e)
raise
if not valid:
raise BadSignatureError()
return msg

+ 348
- 0
nano25519/nano25519/basic.py View File

@@ -0,0 +1,348 @@
import binascii, hashlib, itertools
from hashlib import blake2b

Q = 2**255 - 19
L = 2**252 + 27742317777372353535851937790883648493

def inv(x):
return pow(x, Q-2, Q)

d = -121665 * inv(121666)
I = pow(2,(Q-1)//4,Q)

def xrecover(y):
xx = (y*y-1) * inv(d*y*y+1)
x = pow(xx,(Q+3)//8,Q)
if (x*x - xx) % Q != 0: x = (x*I) % Q
if x % 2 != 0: x = Q-x
return x

By = 4 * inv(5)
Bx = xrecover(By)
B = [Bx % Q,By % Q]

# Extended Coordinates: x=X/Z, y=Y/Z, x*y=T/Z
# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html

def xform_affine_to_extended(pt):
(x, y) = pt
return (x%Q, y%Q, 1, (x*y)%Q) # (X,Y,Z,T)

def xform_extended_to_affine(pt):
(x, y, z, _) = pt
return ((x*inv(z))%Q, (y*inv(z))%Q)

def double_element(pt): # extended->extended
# dbl-2008-hwcd
(X1, Y1, Z1, _) = pt
A = (X1*X1)
B = (Y1*Y1)
C = (2*Z1*Z1)
D = (-A) % Q
J = (X1+Y1) % Q
E = (J*J-A-B) % Q
G = (D+B) % Q
F = (G-C) % Q
H = (D-B) % Q
X3 = (E*F) % Q
Y3 = (G*H) % Q
Z3 = (F*G) % Q
T3 = (E*H) % Q
return (X3, Y3, Z3, T3)

def add_elements(pt1, pt2): # extended->extended
# add-2008-hwcd-3 . Slightly slower than add-2008-hwcd-4, but -3 is
# unified, so it's safe for general-purpose addition
(X1, Y1, Z1, T1) = pt1
(X2, Y2, Z2, T2) = pt2
A = ((Y1-X1)*(Y2-X2)) % Q
B = ((Y1+X1)*(Y2+X2)) % Q
C = T1*(2*d)*T2 % Q
D = Z1*2*Z2 % Q
E = (B-A) % Q
F = (D-C) % Q
G = (D+C) % Q
H = (B+A) % Q
X3 = (E*F) % Q
Y3 = (G*H) % Q
T3 = (E*H) % Q
Z3 = (F*G) % Q
return (X3, Y3, Z3, T3)

def scalarmult_element_safe_slow(pt, n):
# this form is slightly slower, but tolerates arbitrary points, including
# those which are not in the main 1*L subgroup. This includes points of
# order 1 (the neutral element Zero), 2, 4, and 8.
assert n >= 0
if n==0:
return xform_affine_to_extended((0,1))
_ = double_element(scalarmult_element_safe_slow(pt, n>>1))
return add_elements(_, pt) if n&1 else _

def _add_elements_nonunfied(pt1, pt2): # extended->extended
# add-2008-hwcd-4 : NOT unified, only for pt1!=pt2. About 10% faster than
# the (unified) add-2008-hwcd-3, and safe to use inside scalarmult if you
# aren't using points of order 1/2/4/8
(X1, Y1, Z1, T1) = pt1
(X2, Y2, Z2, T2) = pt2
A = ((Y1-X1)*(Y2+X2)) % Q
B = ((Y1+X1)*(Y2-X2)) % Q
C = (Z1*2*T2) % Q
D = (T1*2*Z2) % Q
E = (D+C) % Q
F = (B-A) % Q
G = (B+A) % Q
H = (D-C) % Q
X3 = (E*F) % Q
Y3 = (G*H) % Q
Z3 = (F*G) % Q
T3 = (E*H) % Q
return (X3, Y3, Z3, T3)

def scalarmult_element(pt, n): # extended->extended
# This form only works properly when given points that are a member of
# the main 1*L subgroup. It will give incorrect answers when called with
# the points of order 1/2/4/8, including point Zero. (it will also work
# properly when given points of order 2*L/4*L/8*L)
assert n >= 0
if n==0:
return xform_affine_to_extended((0,1))
_ = double_element(scalarmult_element(pt, n>>1))
return _add_elements_nonunfied(_, pt) if n&1 else _

# points are encoded as 32-bytes little-endian, b255 is sign, b2b1b0 are 0

def encodepoint(P):
x = P[0]
y = P[1]
# MSB of output equals x.b0 (=x&1)
# rest of output is little-endian y
assert 0 <= y < (1<<255) # always < 0x7fff..ff
if x & 1:
y += 1<<255
return binascii.unhexlify("%064x" % y)[::-1]

def isoncurve(P):
x = P[0]
y = P[1]
return (-x*x + y*y - 1 - d*x*x*y*y) % Q == 0

class NotOnCurve(Exception):
pass

def decodepoint(s):
unclamped = int(binascii.hexlify(s[:32][::-1]), 16)
clamp = (1 << 255) - 1
y = unclamped & clamp # clear MSB
x = xrecover(y)
if bool(x & 1) != bool(unclamped & (1<<255)): x = Q-x
P = [x,y]
if not isoncurve(P): raise NotOnCurve("decoding point that is not on curve")
return P

# scalars are encoded as 32-bytes little-endian

def bytes_to_scalar(s):
assert len(s) == 32, len(s)
return int(binascii.hexlify(s[::-1]), 16)

def bytes_to_clamped_scalar(s):
# Ed25519 private keys clamp the scalar to ensure two things:
# 1: integer value is in L/2 .. L, to avoid small-logarithm
# non-wraparaound
# 2: low-order 3 bits are zero, so a small-subgroup attack won't learn
# any information
# set the top two bits to 01, and the bottom three to 000
a_unclamped = bytes_to_scalar(s)
AND_CLAMP = (1<<254) - 1 - 7
OR_CLAMP = (1<<254)
a_clamped = (a_unclamped & AND_CLAMP) | OR_CLAMP
return a_clamped

def random_scalar(entropy_f): # 0..L-1 inclusive
# reduce the bias to a safe level by generating 256 extra bits
oversized = int(binascii.hexlify(entropy_f(32+32)), 16)
return oversized % L

def password_to_scalar(pw):
#oversized = hashlib.sha512(pw).digest()
oversized = blake2b(pw).digest()
return int(binascii.hexlify(oversized), 16) % L

def scalar_to_bytes(y):
y = y % L
assert 0 <= y < 2**256
return binascii.unhexlify("%064x" % y)[::-1]

# Elements, of various orders

def is_extended_zero(XYTZ):
# catch Zero
(X, Y, Z, T) = XYTZ
Y = Y % Q
Z = Z % Q
if X==0 and Y==Z and Y!=0:
return True
return False

class ElementOfUnknownGroup:
# This is used for points of order 2,4,8,2*L,4*L,8*L
def __init__(self, XYTZ):
assert isinstance(XYTZ, tuple)
assert len(XYTZ) == 4
self.XYTZ = XYTZ

def add(self, other):
if not isinstance(other, ElementOfUnknownGroup):
raise TypeError("elements can only be added to other elements")
sum_XYTZ = add_elements(self.XYTZ, other.XYTZ)
if is_extended_zero(sum_XYTZ):
return Zero
return ElementOfUnknownGroup(sum_XYTZ)

def scalarmult(self, s):
if isinstance(s, ElementOfUnknownGroup):
raise TypeError("elements cannot be multiplied together")
assert s >= 0
product = scalarmult_element_safe_slow(self.XYTZ, s)
return ElementOfUnknownGroup(product)

def to_bytes(self):
return encodepoint(xform_extended_to_affine(self.XYTZ))
def __eq__(self, other):
return self.to_bytes() == other.to_bytes()
def __ne__(self, other):
return not self == other

class Element(ElementOfUnknownGroup):
# this only holds elements in the main 1*L subgroup. It never holds Zero,
# or elements of order 1/2/4/8, or 2*L/4*L/8*L.

def add(self, other):
if not isinstance(other, ElementOfUnknownGroup):
raise TypeError("elements can only be added to other elements")
sum_element = ElementOfUnknownGroup.add(self, other)
if sum_element is Zero:
return sum_element
if isinstance(other, Element):
# adding two subgroup elements results in another subgroup
# element, or Zero, and we've already excluded Zero
return Element(sum_element.XYTZ)
# not necessarily a subgroup member, so assume not
return sum_element

def scalarmult(self, s):
if isinstance(s, ElementOfUnknownGroup):
raise TypeError("elements cannot be multiplied together")
# scalarmult of subgroup members can be done modulo the subgroup
# order, and using the faster non-unified function.
s = s % L
# scalarmult(s=0) gets you Zero
if s == 0:
return Zero
# scalarmult(s=1) gets you self, which is a subgroup member
# scalarmult(s<grouporder) gets you a different subgroup member
return Element(scalarmult_element(self.XYTZ, s))

# negation and subtraction only make sense for the main subgroup
def negate(self):
# slow. Prefer e.scalarmult(-pw) to e.scalarmult(pw).negate()
return Element(scalarmult_element(self.XYTZ, L-2))
def subtract(self, other):
return self.add(other.negate())

class _ZeroElement(ElementOfUnknownGroup):
def add(self, other):
return other # zero+anything = anything
def scalarmult(self, s):
return self # zero*anything = zero
def negate(self):
return self # -zero = zero
def subtract(self, other):
return self.add(other.negate())


Base = Element(xform_affine_to_extended(B))
Zero = _ZeroElement(xform_affine_to_extended((0,1))) # the neutral (identity) element

_zero_bytes = Zero.to_bytes()


def arbitrary_element(seed): # unknown DL
# TODO: if we don't need uniformity, maybe use just sha256 here?
hseed = hashlib.sha512(seed).digest()
y = int(binascii.hexlify(hseed), 16) % Q

# we try successive Y values until we find a valid point
for plus in itertools.count(0):
y_plus = (y + plus) % Q
x = xrecover(y_plus)
Pa = [x,y_plus] # no attempt to use both "positive" and "negative" X

# only about 50% of Y coordinates map to valid curve points (I think
# the other half give you points on the "twist").
if not isoncurve(Pa):
continue

P = ElementOfUnknownGroup(xform_affine_to_extended(Pa))
# even if the point is on our curve, it may not be in our particular
# (order=L) subgroup. The curve has order 8*L, so an arbitrary point
# could have order 1,2,4,8,1*L,2*L,4*L,8*L (everything which divides
# the group order).

# [I MAY BE COMPLETELY WRONG ABOUT THIS, but my brief statistical
# tests suggest it's not too far off] There are phi(x) points with
# order x, so:
# 1 element of order 1: [(x=0,y=1)=Zero]
# 1 element of order 2 [(x=0,y=-1)]
# 2 elements of order 4
# 4 elements of order 8
# L-1 elements of order L (including Base)
# L-1 elements of order 2*L
# 2*(L-1) elements of order 4*L
# 4*(L-1) elements of order 8*L

# So 50% of random points will have order 8*L, 25% will have order
# 4*L, 13% order 2*L, and 13% will have our desired order 1*L (and a
# vanishingly small fraction will have 1/2/4/8). If we multiply any
# of the 8*L points by 2, we're sure to get an 4*L point (and
# multiplying a 4*L point by 2 gives us a 2*L point, and so on).
# Multiplying a 1*L point by 2 gives us a different 1*L point. So
# multiplying by 8 gets us from almost any point into a uniform point
# on the correct 1*L subgroup.

P8 = P.scalarmult(8)

# if we got really unlucky and picked one of the 8 low-order points,
# multiplying by 8 will get us to the identity (Zero), which we check
# for explicitly.
if is_extended_zero(P8.XYTZ):
continue

# Test that we're finally in the right group. We want to scalarmult
# by L, and we want to *not* use the trick in Group.scalarmult()
# which does x%L, because that would bypass the check we care about.
# P is still an _ElementOfUnknownGroup, which doesn't use x%L because
# that's not correct for points outside the main group.
assert is_extended_zero(P8.scalarmult(L).XYTZ)

return Element(P8.XYTZ)
# never reached

def bytes_to_unknown_group_element(bytes):
# this accepts all elements, including Zero and wrong-subgroup ones
if bytes == _zero_bytes:
return Zero
XYTZ = xform_affine_to_extended(decodepoint(bytes))
return ElementOfUnknownGroup(XYTZ)

def bytes_to_element(bytes):
# this strictly only accepts elements in the right subgroup
P = bytes_to_unknown_group_element(bytes)
if P is Zero:
raise ValueError("element was Zero")
if not is_extended_zero(P.scalarmult(L).XYTZ):
raise ValueError("element is not in the right group")
# the point is in the expected 1*L subgroup, not in the 2/4/8 groups,
# or in the 2*L/4*L/8*L groups. Promote it to a correct-group Element.
return Element(P.XYTZ)

+ 16
- 0
nano25519/nano25519/dh.py View File

@@ -0,0 +1,16 @@
from .basic import random_scalar, Base, bytes_to_element
#from hashlib import sha256
from pyblake2 import blake2b

# In practice, you should use the Curve25519 function, which is better in
# every way. But this is an example of what Diffie-Hellman looks like.

def dh_start(entropy_f):
x = random_scalar(entropy_f)
X = Base.scalarmult(x)
return x,X.to_bytes()

def dh_finish(x, Y_s):
Y = bytes_to_element(Y_s)
XY = Y.scalarmult(x)
return blake2b(XY.to_bytes()).digest()

+ 45
- 0
nano25519/nano25519/do_ed25519_kat.py View File

@@ -0,0 +1,45 @@
from __future__ import print_function
import unittest
from binascii import hexlify, unhexlify
from .ed25519_oop import SigningKey, VerifyingKey

class KnownAnswerTests(unittest.TestCase):
def test_all(self):
# kat-ed25519.txt comes from "sign.input" on ed25519.cr.yp.to . The
# pure-python ed25519.py in the same distribution uses a very
# different key format than the one used by NaCl.
lines = list(open("pure25519/kat-ed25519.txt"))
for i,line in enumerate(lines):
if not i%50: print("%d/%d" % (i, len(lines)))
x = line.split(":")
A,B,C,D = [unhexlify(i) for i in x[:4]]
# A[:32] is the 32 byte seed (the entropy input to H())
# A[32:] == B == the public point (pubkey)
# C is the message
# D is 64 bytes of signature (R+S) prepended to the message

seed = A[:32]
vk_s = B
# the NaCl signature is R+S, which happens to be the same as ours
msg = C
sig = D[:64] # R+S
# note that R depends only upon the second half of H(seed). S
# depends upon both the first half (the exponent) and the second
# half

#if len(msg) % 16 == 1:
# print "msg len = %d" % len(msg), time.time()

sk = SigningKey(seed)
vk = sk.get_verifying_key()
self.failUnlessEqual(vk.to_bytes(), vk_s)
vk2 = VerifyingKey(vk_s)
self.failUnlessEqual(vk2, vk) # objects should compare equal
self.failUnlessEqual(vk2.to_bytes(), vk_s)
newsig = sk.sign(msg) # R+S
self.failUnlessEqual(hexlify(newsig), hexlify(sig)) # deterministic sigs
self.failUnlessEqual(vk.verify(sig, msg), None) # no exception


if __name__ == '__main__':
unittest.main()

+ 191
- 0
nano25519/nano25519/ed25519_oop.py View File

@@ -0,0 +1,191 @@
import os
import base64
from . import _ed25519
BadSignatureError = _ed25519.BadSignatureError

def create_keypair(entropy=os.urandom):
SEEDLEN = int(_ed25519.SECRETKEYBYTES/2)
assert SEEDLEN == 32
seed = entropy(SEEDLEN)
sk = SigningKey(seed)
vk = sk.get_verifying_key()
return sk, vk

class BadPrefixError(Exception):
pass

def remove_prefix(s_bytes, prefix):
assert(type(s_bytes) == type(prefix))
if s_bytes[:len(prefix)] != prefix:
raise BadPrefixError("did not see expected '%s' prefix" % (prefix,))
return s_bytes[len(prefix):]

def to_ascii(s_bytes, prefix="", encoding="base64"):
"""Return a version-prefixed ASCII representation of the given binary
string. 'encoding' indicates how to do the encoding, and can be one of:
* base64
* base32
* base16 (or hex)

This function handles bytes, not bits, so it does not append any trailing
'=' (unlike standard base64.b64encode). It also lowercases the base32
output.

'prefix' will be prepended to the encoded form, and is useful for
distinguishing the purpose and version of the binary string. E.g. you
could prepend 'pub0-' to a VerifyingKey string to allow the receiving
code to raise a useful error if someone pasted in a signature string by
mistake.
"""
assert isinstance(s_bytes, bytes)
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
if encoding == "base64":
s_ascii = base64.b64encode(s_bytes).decode('ascii').rstrip("=")
elif encoding == "base32":
s_ascii = base64.b32encode(s_bytes).decode('ascii').rstrip("=").lower()
elif encoding in ("base16", "hex"):
s_ascii = base64.b16encode(s_bytes).decode('ascii').lower()
else:
raise NotImplementedError
return prefix+s_ascii.encode('ascii')

def from_ascii(s_ascii, prefix="", encoding="base64"):
"""This is the opposite of to_ascii. It will throw BadPrefixError if
the prefix is not found.
"""
if isinstance(s_ascii, bytes):
s_ascii = s_ascii.decode('ascii')
if isinstance(prefix, bytes):
prefix = prefix.decode('ascii')
s_ascii = remove_prefix(s_ascii.strip(), prefix)
if encoding == "base64":
s_ascii += "="*((4 - len(s_ascii)%4)%4)
s_bytes = base64.b64decode(s_ascii)
elif encoding == "base32":
s_ascii += "="*((8 - len(s_ascii)%8)%8)
s_bytes = base64.b32decode(s_ascii.upper())
elif encoding in ("base16", "hex"):
s_bytes = base64.b16decode(s_ascii.upper())
else:
raise NotImplementedError
return s_bytes

class SigningKey(object):
# this can only be used to reconstruct a key created by create_keypair().
def __init__(self, sk_s, prefix="", encoding=None):
assert isinstance(sk_s, bytes)
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
sk_s = remove_prefix(sk_s, prefix)
if encoding is not None:
sk_s = from_ascii(sk_s, encoding=encoding)
if len(sk_s) == 32:
# create from seed
vk_s, sk_s = _ed25519.publickey(sk_s)
else:
if len(sk_s) != 32+32:
raise ValueError("SigningKey takes 32-byte seed or 64-byte string")
self.sk_s = sk_s # seed+pubkey
self.vk_s = sk_s[32:] # just pubkey

def to_bytes(self, prefix=""):
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
return prefix+self.sk_s

def to_ascii(self, prefix="", encoding=None):
assert encoding
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
return to_ascii(self.to_seed(), prefix, encoding)

def to_seed(self, prefix=""):
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
return prefix+self.sk_s[:32]

def __eq__(self, them):
if not isinstance(them, object): return False
return (them.__class__ == self.__class__
and them.sk_s == self.sk_s)

def get_verifying_key(self):
return VerifyingKey(self.vk_s)

def sign(self, msg, prefix="", encoding=None):
assert isinstance(msg, bytes)
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
sig_and_msg = _ed25519.sign(msg, self.sk_s)
# the response is R+S+msg
sig_R = sig_and_msg[0:32]
sig_S = sig_and_msg[32:64]
msg_out = sig_and_msg[64:]
sig_out = sig_R + sig_S
assert msg_out == msg
if encoding:
return to_ascii(sig_out, prefix, encoding)
return prefix+sig_out

class VerifyingKey(object):
def __init__(self, vk_s, prefix="", encoding=None):
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
if not isinstance(vk_s, bytes):
vk_s = vk_s.encode('ascii')
assert isinstance(vk_s, bytes)
vk_s = remove_prefix(vk_s, prefix)
if encoding is not None:
vk_s = from_ascii(vk_s, encoding=encoding)

assert len(vk_s) == 32
self.vk_s = vk_s

def to_bytes(self, prefix=""):
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
return prefix+self.vk_s

def to_ascii(self, prefix="", encoding=None):
assert encoding
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
return to_ascii(self.vk_s, prefix, encoding)

def __eq__(self, them):
if not isinstance(them, object): return False
return (them.__class__ == self.__class__
and them.vk_s == self.vk_s)

def verify(self, sig, msg, prefix="", encoding=None):
if not isinstance(sig, bytes):
sig = sig.encode('ascii')
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
assert isinstance(sig, bytes)
assert isinstance(msg, bytes)
if encoding:
sig = from_ascii(sig, prefix, encoding)
else:
sig = remove_prefix(sig, prefix)
assert len(sig) == 64
sig_R = sig[:32]
sig_S = sig[32:]
sig_and_msg = sig_R + sig_S + msg
# this might raise BadSignatureError
msg2 = _ed25519.open(sig_and_msg, self.vk_s)
assert msg2 == msg

def selftest():
message = b"crypto libraries should always test themselves at powerup"
sk = SigningKey(b"priv0-VIsfn5OFGa09Un2MR6Hm7BQ5++xhcQskU2OGXG8jSJl4cWLZrRrVcSN2gVYMGtZT+3354J5jfmqAcuRSD9KIyg",
prefix="priv0-", encoding="base64")
vk = VerifyingKey(b"pub0-eHFi2a0a1XEjdoFWDBrWU/t9+eCeY35qgHLkUg/SiMo",
prefix="pub0-", encoding="base64")
assert sk.get_verifying_key() == vk
sig = sk.sign(message, prefix="sig0-", encoding="base64")
assert sig == b"sig0-E/QrwtSF52x8+q0l4ahA7eJbRKc777ClKNg217Q0z4fiYMCdmAOI+rTLVkiFhX6k3D+wQQfKdJYMxaTUFfv1DQ", sig
vk.verify(sig, message, prefix="sig0-", encoding="base64")

#selftest()

+ 73
- 0
nano25519/nano25519/eddsa.py View File

@@ -0,0 +1,73 @@

from .basic import (bytes_to_clamped_scalar,
bytes_to_scalar, scalar_to_bytes,
bytes_to_element, Base)
import hashlib, binascii
from hashlib import blake2b

def H(m):
#return hashlib.sha512(m).digest()
return blake2b(m).digest()

def publickey(seed):
# turn first half of SHA512(seed) into scalar, then into point
assert len(seed) == 32
a = bytes_to_clamped_scalar(H(seed)[:32])
A = Base.scalarmult(a)
return A.to_bytes()

def Hint(m):
h = H(m)
return int(binascii.hexlify(h[::-1]), 16)

def signature(m,sk,pk):
assert len(sk) == 32 # seed
assert len(pk) == 32
h = H(sk[:32])
a_bytes, inter = h[:32], h[32:]
a = bytes_to_clamped_scalar(a_bytes)
r = Hint(inter + m)
R = Base.scalarmult(r)
R_bytes = R.to_bytes()
S = r + Hint(R_bytes + pk + m) * a
return R_bytes + scalar_to_bytes(S)

def checkvalid(s, m, pk):
if len(s) != 64: raise Exception("signature length is wrong")
if len(pk) != 32: raise Exception("public-key length is wrong")
R = bytes_to_element(s[:32])
A = bytes_to_element(pk)
S = bytes_to_scalar(s[32:])
h = Hint(s[:32] + pk + m)
v1 = Base.scalarmult(S)
v2 = R.add(A.scalarmult(h))
return v1==v2

# wrappers

import os

def create_signing_key():
seed = os.urandom(32)
return seed
def create_verifying_key(signing_key):
return publickey(signing_key)

def sign(skbytes, msg):
"""Return just the signature, given the message and just the secret
key."""
if len(skbytes) != 32:
raise ValueError("Bad signing key length %d" % len(skbytes))
vkbytes = create_verifying_key(skbytes)
sig = signature(msg, skbytes, vkbytes)
return sig

def verify(vkbytes, sig, msg):
if len(vkbytes) != 32:
raise ValueError("Bad verifying key length %d" % len(vkbytes))
if len(sig) != 64:
raise ValueError("Bad signature length %d" % len(sig))
rc = checkvalid(sig, msg, vkbytes)
if not rc:
raise ValueError("rc != 0", rc)
return True

+ 1025
- 0
nano25519/nano25519/kat-ed25519.txt
File diff suppressed because it is too large
View File


+ 47
- 0
nano25519/nano25519/slow_basic.py View File

@@ -0,0 +1,47 @@

from .basic import (inv, d, Q, L,
xform_extended_to_affine,
scalarmult_element,
xform_affine_to_extended,
double_element, _add_elements_nonunfied)

# Affine Coordinates: only here to compare against faster versions

def slow_add_affine(A,B): # affine->affine
# Complete: works even when A==B. Slow: 50x slower than extended
x1 = A[0]
y1 = A[1]
x2 = B[0]
y2 = B[1]
x3 = (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2)
y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2)
return (x3 % Q,y3 % Q)

def slow_scalarmult_affine(A,e): # affine->affine
e = e % L
if e == 0: return [0,1]
B = slow_scalarmult_affine(A,e//2)
B = slow_add_affine(B,B)
if e & 1: B = slow_add_affine(B,A)
return B

# other functions that are only here for speed comparisons

def scalarmult_affine(pt, e): # affine->affine
e = e % L
return xform_extended_to_affine(
scalarmult_element(
xform_affine_to_extended(pt),
e))

def scalarmult_affine_to_extended(pt, n): # affine->extended
assert len(pt) == 2 # affine
n = n % L
if n==0: return xform_affine_to_extended((0,1))
xpt = xform_affine_to_extended(pt) # so Z=1
return _scalarmult_affine_to_extended_inner(xpt, n)

def _scalarmult_affine_to_extended_inner(xpt, n):
if n==0: return xform_affine_to_extended((0,1))
_ = double_element(_scalarmult_affine_to_extended_inner(xpt, n>>1))
return _add_elements_nonunfied(_, xpt) if n&1 else _

+ 48
- 0
nano25519/nano25519/spake2.py View File

@@ -0,0 +1,48 @@
from hashlib import sha256
from pyblake2 import blake2b
from .basic import (arbitrary_element, bytes_to_element, Base,
random_scalar, password_to_scalar)

# a,b random. X=G*a+U*pw. Y=G*b+V*pw. Z1=(Y-V*pw)*a. Z2=(X-U*pw)*b

U = arbitrary_element(b"U")
V = arbitrary_element(b"V")

def _start(pw, entropy_f, blinding):
a = random_scalar(entropy_f)
pw_scalar = password_to_scalar(pw)
X = Base.scalarmult(a).add(blinding.scalarmult(pw_scalar))
X_s = X.to_bytes()
return (a, pw_scalar), X_s

def _finish(start_data, Y_s, blinding):
(a, pw_scalar) = start_data
Y = bytes_to_element(Y_s) # rejects zero and non-group
Z = Y.add(blinding.scalarmult(-pw_scalar)).scalarmult(a)
return Z.to_bytes()


def start_U(pw, entropy_f, idA, idB, U=U, V=V):
sdata, X_s = _start(pw, entropy_f, U)
start_data = (sdata, pw, X_s, idA, idB, U, V)
return start_data, X_s

def finish_U(start_data, Y_s):
(sdata, pw, X_s, idA, idB, U, V) = start_data
Z_s = _finish(sdata, Y_s, V)
transcript = idA + idB + X_s + Y_s + Z_s + pw
key = blake2b(transcript).digest()
return key


def start_V(pw, entropy_f, idA, idB, U=U, V=V):
sdata, Y_s = _start(pw, entropy_f, V)
start_data = (sdata, pw, Y_s, idA, idB, U, V)
return start_data, Y_s

def finish_V(start_data, X_s):
(sdata, pw, Y_s, idA, idB, U, V) = start_data
Z_s = _finish(sdata, X_s, U)
transcript = idA + idB + X_s + Y_s + Z_s + pw
key = blake2b(transcript).digest()
return key

+ 94
- 0
nano25519/nano25519/speed_basic.py View File

@@ -0,0 +1,94 @@
import timeit

def do(setup_statements, statement):
# extracted from timeit.py
t = timeit.Timer(stmt=statement,
setup="\n".join(setup_statements))
# determine number so that 0.2 <= total time < 2.0
for i in range(1, 10):
number = 10**i
x = t.timeit(number)
if x >= 1.0:
break
return x / number

def abbrev(t):
if t > 1.0:
return "%.3fs" % t
if t > 1e-3:
return "%.2fms" % (t*1e3)
if t > 1e-6:
return "%.2fus" % (t*1e6)
return "%.2fns" % (t*1e9)

def p(name, setup_statements, statements):
t = sorted([do(setup_statements, statements) for i in range(3)])
print("%-32s: %s (%s)" % (name,
abbrev(min(t)),
" ".join([abbrev(s) for s in t])))

# pure_ed25519.sign() is doing an extra publickey(), doubles the cost

def run():
S1 = "from pure25519 import basic, slow_basic"
S2 = "p=slow_basic.scalarmult_affine(basic.B, 16*1000000000)"
S3 = "P=basic.encodepoint(p)"
S4 = "basic.decodepoint(P)"
S5big = r"i = b'\xf0'+b'\xff'*31"
S5medium = r"i = b'\xf0'+b'\x55'*31"
S5small = r"i = b'\xf0'+b'\x00'*31"
S6 = "si=basic.bytes_to_scalar(i)"
S7 = "basic.scalar_to_bytes(si)"
S8 = "slow_basic.slow_scalarmult_affine(p, si)"
S9 = "slow_basic.scalarmult_affine(p, si)"
S10 = "x=basic.xform_affine_to_extended(p)"
S11 = "basic.xform_extended_to_affine(x)"
S12 = "p2 = slow_basic.scalarmult_affine(basic.B, 17)"
S13 = "slow_basic.slow_add_affine(p, p2)"
S14 = "x2=basic.xform_affine_to_extended(p2)"
S15 = "basic.add_elements(x, x2)"
S16 = "basic.xrecover(basic.Bx)"
S17 = "basic.isoncurve(p)"
S18 = "(si + si) % basic.Q"
S19 = "(si * si) % basic.Q"
S20 = "basic.inv(si)"
S21 = "basic._add_elements_nonunfied(x, x2)"
S22 = "e=basic.bytes_to_unknown_group_element(P)"
S23 = "e=basic.bytes_to_element(P)"
S24 = "e=basic.arbitrary_element(b'seed')"
S25 = "e.scalarmult(si)"

print("speed_basic")
if 1:
p("encodepoint", [S1,S2], S3)
p("decodepoint", [S1,S2,S3], S4)
p("scalar_to_bytes", [S1,S5big,S6], S7)
p("bytes_to_scalar", [S1,S5big], S6)
p("slow_scalarmult_affine (big)", [S1,S2,S5big,S6], S8)
p("slow_scalarmult_affine (medium)", [S1,S2,S5medium,S6], S8)
p("slow_scalarmult_affine (small)", [S1,S2,S5small,S6], S8)
p("scalarmult_affine (big)", [S1,S2,S5big,S6], S9)
p("scalarmult_affine (medium)", [S1,S2,S5medium,S6], S9)
p("scalarmult_affine (small)", [S1,S2,S5small,S6], S9)
p("xform_affine_to_extended", [S1,S2], S10)
p("xform_extended_to_affine", [S1,S2,S10], S11)
p("slow_add_affine", [S1,S2,S12], S13)
p("add (extended)", [S1,S2,S12,S10,S14], S15)
p("add_nonunified (extended)", [S1,S2,S12,S10,S14], S21)
p("xrecover", [S1], S16)
p("isoncurve", [S1,S2], S17)
if 1:
p("field_add", [S1,S5medium,S6], S18)
p("field_mul (big)", [S1,S5big,S6], S19)
p("field_mul (medium)", [S1,S5medium,S6], S19)
p("field_mul (small)", [S1,S5small,S6], S19)
p("field_inv", [S1,S5medium,S6], S20)
if 1:
p("bytes_to_unknown_group_element", [S1,S2,S3], S22)
p("bytes_to_element", [S1,S2,S3], S23)
p("arbitrary_element", [S1], S24)
p("scalarmult(unknown-medium)", [S1,S2,S3,S22,S5medium,S6], S25)
p("scalarmult(medium)", [S1,S2,S3,S23,S5medium,S6], S25)

if __name__ == "__main__":
run()

+ 39
- 0
nano25519/nano25519/speed_dh.py View File

@@ -0,0 +1,39 @@
import timeit

def do(setup_statements, statement):
# extracted from timeit.py
t = timeit.Timer(stmt=statement,
setup="\n".join(setup_statements))
# determine number so that 0.2 <= total time < 2.0
for i in range(1, 10):
number = 10**i
x = t.timeit(number)
if x >= 0.5:
break
return x / number

def abbrev(t):
if t > 1.0:
return "%.3fs" % t
if t > 1e-3:
return "%.2fms" % (t*1e3)
return "%.2fus" % (t*1e6)

def p(name, setup_statements, statements):
t = sorted([do(setup_statements, statements) for i in range(3)])
print("%12s: %s (%s)" % (name,
abbrev(min(t)),
" ".join([abbrev(s) for s in t])))

def run():
S1 = "import os; from pure25519 import dh"
S2 = "x,X_s = dh.dh_start(os.urandom)"
S3 = "y,Y_s = dh.dh_start(os.urandom)"
S4 = "dh.dh_finish(x,Y_s)"

print("speed_dh")
p("start", [S1], S2)
p("finish", [S1, S2, S3], S4)

if __name__ == "__main__":
run()

+ 47
- 0
nano25519/nano25519/speed_ed25519.py View File

@@ -0,0 +1,47 @@
import timeit

def do(setup_statements, statement):
# extracted from timeit.py
t = timeit.Timer(stmt=statement,
setup="\n".join(setup_statements))
# determine number so that 1.0 <= total time < 10.0
for i in range(1, 10):
number = 10**i
x = t.timeit(number)
if x >= 1.0:
break
return x / number

def abbrev(t):
if t > 1.0:
return "%.3fs" % t
if t > 1e-3:
return "%.2fms" % (t*1e3)
return "%.2fus" % (t*1e6)

def p(name, setup_statements, statements):
t = sorted([do(setup_statements, statements) for i in range(3)])
print("%12s: %s (%s)" % (name,
abbrev(min(t)),
" ".join([abbrev(s) for s in t])))

def run():
S1 = "from pure25519 import ed25519_oop; msg=b'hello world'"
S2 = "sk,vk = ed25519_oop.create_keypair()"
S3 = "sig = sk.sign(msg)"
S4 = "vk.verify(sig, msg)"

print("speed_ed25519")
p("generate", [S1], S2)
p("sign", [S1, S2], S3)
p("verify", [S1, S2, S3], S4)

S5 = "from pure25519 import eddsa"
S6 = "h=eddsa.Hint(b'')"
S7 = "eddsa.checkvalid(sig, msg, vk.vk_s)"

p("Hint", [S5], S6)
p("checkvalid", [S1,S2,S3,S5], S7)

if __name__ == "__main__":
run()

+ 39
- 0
nano25519/nano25519/speed_spake2.py View File

@@ -0,0 +1,39 @@
import timeit

def do(setup_statements, statement):
# extracted from timeit.py
t = timeit.Timer(stmt=statement,
setup="\n".join(setup_statements))
# determine number so that 0.2 <= total time < 2.0
for i in range(1, 10):
number = 10**i
x = t.timeit(number)
if x >= 0.5:
break
return x / number

def abbrev(t):
if t > 1.0:
return "%.3fs" % t
if t > 1e-3:
return "%.2fms" % (t*1e3)
return "%.2fus" % (t*1e6)

def p(name, setup_statements, statements):
t = sorted([do(setup_statements, statements) for i in range(3)])
print("%12s: %s (%s)" % (name,
abbrev(min(t)),
" ".join([abbrev(s) for s in t])))

def run():
S1 = "import os; from pure25519 import spake2; pw=b'pw'; A=b'idA'; B=b'idB'"
S2 = "sdata_U,X_s = spake2.start_U(pw, os.urandom, A, B)"
S3 = "sdata_V,Y_s = spake2.start_V(pw, os.urandom, A, B)"
S4 = "k = spake2.finish_U(sdata_U,Y_s)"

print("speed_spake2")
p("start", [S1], S2)
p("finish", [S1, S2, S3], S4)

if __name__ == "__main__":
run()

+ 157
- 0
nano25519/nano25519/test_basic.py View File

@@ -0,0 +1,157 @@
import unittest
import random
from binascii import hexlify
from pure25519.basic import (Q, L, B, ElementOfUnknownGroup,
arbitrary_element, password_to_scalar,
bytes_to_element, bytes_to_unknown_group_element,
_add_elements_nonunfied, add_elements, encodepoint,
xform_extended_to_affine, xform_affine_to_extended)
from pure25519.basic import Base, Element, Zero
from pure25519.slow_basic import (slow_add_affine, scalarmult_affine,
scalarmult_affine_to_extended)

class Basic(unittest.TestCase):
def assertElementsEqual(self, e1, e2, msg=None):
self.assertEqual(hexlify(e1.to_bytes()), hexlify(e2.to_bytes()),
msg)
def assertBytesEqual(self, e1, e2, msg=None):
self.assertEqual(hexlify(e1), hexlify(e2), msg)

def test_arbitrary_element(self):
for i in range(20):
seed = str(i).encode("ascii")
e = arbitrary_element(seed)
e2 = arbitrary_element(seed)
self.assertElementsEqual(e, e2)

def test_password_to_scalar(self):
for i in range(20):
seed = str(i).encode("ascii")
s = password_to_scalar(seed)
s2 = password_to_scalar(seed)
self.assertEqual(s, s2)

def test_scalarmult(self):
Bsm = Base.scalarmult
e0 = Bsm(0)
e1 = Bsm(1)
e2 = Bsm(2)
e5 = Bsm(5)
e10 = Bsm(10)
e15 = Bsm(15)
em5 = Bsm(-5)
self.assertElementsEqual(e0.add(e0), e0)
self.assertElementsEqual(e1, Base)
self.assertElementsEqual(e0.add(e1), e1)
self.assertElementsEqual(Bsm(-5), Bsm(-5 % L))
# there was a bug, due to the add inside scalarmult being
# non-unified, which caused e0.scalarmult(N) to not equal e0
self.assertElementsEqual(e0.scalarmult(5), e0)
self.assertElementsEqual(e2.scalarmult(0), e0)
self.assertElementsEqual(e1.add(e1), e2)
self.assertElementsEqual(e5.add(e5), e10)
self.assertElementsEqual(e5.add(e10), e15)
self.assertElementsEqual(e2.scalarmult(5), e10)
self.assertElementsEqual(e15.add(em5), e10)
self.assertElementsEqual(e5.add(em5), e0)

sm2 = scalarmult_affine
e = {}
for i in range(-50, 100):
e[i] = Bsm(i)
self.assertElementsEqual(Element(xform_affine_to_extended(sm2(B, i))), e[i])
self.assertElementsEqual(Element(scalarmult_affine_to_extended(B, i)),
e[i])

for i in range(20,30):
for j in range(-10,10):
x1 = e[i].add(e[j])
x2 = Bsm(i+j)
self.assertElementsEqual(x1, x2, (x1,x2,i,j))
x3 = Element(xform_affine_to_extended(sm2(B, i+j)))
self.assertElementsEqual(x1, x3, (x1,x3,i,j))