Initial commit

This commit is contained in:
zyb 2024-06-15 07:18:14 +08:00
commit 82f8dd2c23
36 changed files with 5079 additions and 0 deletions

139
.github/workflows/mikrotik_patch.yml vendored Normal file
View File

@ -0,0 +1,139 @@
name: Patch Mikrotik RouterOS
on:
# push:
# branches: [ "main" ]
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
permissions:
contents: write
jobs:
Patch_Mikrotik_RouterOS:
runs-on: ubuntu-latest
env:
TZ: 'Asia/Shanghai'
LATEST_STABLE_VERSION_URL: 'https://upgrade.mikrotik.com/routeros/NEWESTa7.stable'
LATEST_VERSION: "7.15"
CUSTOM_LICENSE_PRIVATE_KEY: ${{ secrets.CUSTOM_LICENSE_PRIVATE_KEY }}
CUSTOM_LICENSE_PUBLIC_KEY: ${{ secrets.CUSTOM_LICENSE_PUBLIC_KEY }}
CUSTOM_NPK_SIGN_PRIVATE_KEY: ${{ secrets.CUSTOM_NPK_SIGN_PRIVATE_KEY }}
CUSTOM_NPK_SIGN_PUBLIC_KEY: ${{ secrets.CUSTOM_NPK_SIGN_PUBLIC_KEY }}
MIKRO_LICENSE_PUBLIC_KEY: ${{ secrets.MIKRO_LICENSE_PUBLIC_KEY }}
MIKRO_NPK_SIGN_PUBLIC_LKEY: ${{ secrets.MIKRO_NPK_SIGN_PUBLIC_LKEY }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Get latest routeros stable version
run: |
echo $(uname -a)
LATEST_VERSION=$(wget -nv -O - $LATEST_STABLE_VERSION_URL | cut -d ' ' -f1)
echo Latest Stabel Version:$LATEST_VERSION
echo "LATEST_VERSION=${LATEST_VERSION}" >> $GITHUB_ENV
- name: Create keygen
run: |
zip keygen.zip ./keygen.exe
- name: Create squashfs for option npk
run: |
cd $GITHUB_WORKSPACE
sudo wget -O bash -nv https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox_ASH
sudo wget -O busybox -nv https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox
sudo chmod +x busybox
sudo chmod +x bash
sudo mkdir -p ./option-root/bin/
sudo mv busybox ./option-root/bin/
sudo mv bash ./option-root/bin/
COMMANDS=$(./option-root/bin/busybox --list)
for cmd in $COMMANDS; do
sudo ln -sf /pckg/option/bin/busybox ./option-root/bin/$cmd
done
sudo rm -f option.sfs
sudo mksquashfs option-root option.sfs -quiet -comp xz -no-xattrs -b 256k
sudo rm -rf option-root
- name: Get routeros-${{ env.LATEST_VERSION }}.npk
run: sudo wget -nv -O routeros-$LATEST_VERSION.npk https://download.mikrotik.com/routeros/$LATEST_VERSION/routeros-$LATEST_VERSION.npk
- name: Patch routeros-${{ env.LATEST_VERSION }}.npk
run: sudo -E python3 patch.py routeros-$LATEST_VERSION.npk
- name: Get mikrotik-${{ env.LATEST_VERSION }}.iso
run: sudo wget -nv -O mikrotik-$LATEST_VERSION.iso https://download.mikrotik.com/routeros/$LATEST_VERSION/mikrotik-$LATEST_VERSION.iso
- name: Patch mikrotik-${{ env.LATEST_VERSION }}.iso
run: |
sudo apt-get install -y mkisofs > /dev/null
sudo mkdir ./iso
sudo mount -o loop,ro mikrotik-$LATEST_VERSION.iso ./iso
sudo mkdir ./new_iso
sudo cp -r ./iso/* ./new_iso/
sudo rsync -a ./iso/ ./new_iso/
sudo umount ./iso
sudo rm -rf ./iso
sudo rm -f mikrotik-$LATEST_VERSION.iso
sudo rm -rf ./new_iso/routeros-$LATEST_VERSION.npk
NPK_FILES=$(find ./new_iso/*.npk)
for file in $NPK_FILES; do
sudo -E python3 npk.py sign $file $file
done
sudo cp routeros-$LATEST_VERSION.npk ./new_iso/
sudo -E python3 npk.py create ./new_iso/gps-$LATEST_VERSION.npk ./option.sfs ./option-$LATEST_VERSION.npk
sudo cp option-$LATEST_VERSION.npk ./new_iso/
sudo cp linux ./new_iso/isolinux/
sudo mkdir ./efiboot
sudo mount -o loop ./new_iso/efiboot.img ./efiboot
sudo cp linux ./efiboot/linux.x86_64
sudo umount ./efiboot
sudo rm -rf ./efiboot
sudo mkisofs -o mikrotik-$LATEST_VERSION.iso \
-V "MikroTik $LATEST_VERSION Patched" \
-sysid "" -preparer "MiKroTiK" \
-publisher "" -A "MiKroTiK RouterOS" \
-b isolinux/isolinux.bin \
-c isolinux/boot.cat \
-no-emul-boot \
-boot-load-size 4 \
-boot-info-table \
-eltorito-alt-boot \
-e efiboot.img \
-no-emul-boot \
-R \
./new_iso
sudo rm -rf ./new_iso
- name: Delete Release tag ${{ env.LATEST_VERSION }}
run: |
HEADER="Authorization: token ${{ secrets.GITHUB_TOKEN }}"
RELEASE_INFO=$(curl -s -H $HEADER https://api.github.com/repos/${{ github.repository }}/releases/tags/${{ env.LATEST_VERSION }})
RELEASE_ID=$(echo $RELEASE_INFO | jq -r '.id')
echo "Release ID: $RELEASE_ID"
if [ "$RELEASE_ID" != "null" ]; then
curl -X DELETE -H "$HEADER" https://api.github.com/repos/${{ github.repository }}/git/refs/tags/${{ env.LATEST_VERSION }}
echo "Tag ${{ env.LATEST_VERSION }} deleted successfully."
curl -X DELETE -H "$HEADER" https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID
echo "Release with tag ${{ env.LATEST_VERSION }} deleted successfully."
else
echo "Release not found for tag: ${{ env.LATEST_VERSION }})"
fi
- name: Create Release tag ${{ env.LATEST_VERSION }}
uses: softprops/action-gh-release@v2
with:
name: "MikroTik ${{ env.LATEST_VERSION }}"
body: "MikroTik ${{ env.LATEST_VERSION }}"
tag_name: ${{ env.LATEST_VERSION }}
make_latest: "true"
files: |
mikrotik-${{ env.LATEST_VERSION }}.iso
routeros-${{ env.LATEST_VERSION }}.npk
option-${{ env.LATEST_VERSION }}.npk
keygen.zip

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
__pycache__/
venv/

15
README.md Normal file
View File

@ -0,0 +1,15 @@
# Patch MikroTik RouterOS
### Download [Latest Patched](https://github.com/elseif/MikroTikPatch/releases/latest) iso file,install it and enjoy.
![](install.png)
![](routeros.png)
### Uses keygen to generate license key.
![](keygen.png)
### all patches are applied automatically with [github workflow](https://github.com/elseif/MikroTikPatch/blob/main/.github/workflows/mikrotik_patch.yml).

BIN
install.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
keygen.exe Normal file

Binary file not shown.

BIN
keygen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

213
mikro.py Normal file
View File

@ -0,0 +1,213 @@
import struct
from sha256 import SHA256
from toyecc import AffineCurvePoint, getcurvebyname, FieldElement,ECPrivateKey,ECPublicKey,Tools
from toyecc.Random import secure_rand_int_between
MIKRO_LICENSE_HEADER = '-----BEGIN MIKROTIK SOFTWARE KEY------------'
MIKRO_LICENSE_FOOTER = '-----END MIKROTIK SOFTWARE KEY--------------'
MIKRO_BASE64_CHARACTER_TABLE = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
SOFTWARE_ID_CHARACTER_TABLE = b'TN0BYX18S5HZ4IA67DGF3LPCJQRUK9MW2VE'
MIKRO_SHA256_K = (
0x0548D563, 0x98308EAB, 0x37AF7CCC, 0xDFBC4E3C,
0xF125AAC9, 0xEC98ACB8, 0x8B540795, 0xD3E0EF0E,
0x4904D6E5, 0x0DA84981, 0x9A1F8452, 0x00EB7EAA,
0x96F8E3B3, 0xA6CDB655, 0xE7410F9E, 0x8EECB03D,
0x9C6A7C25, 0xD77B072F, 0x6E8F650A, 0x124E3640,
0x7E53785A, 0xE0150772, 0xC61EF4E0, 0xBC57E5E0,
0xC0F9A285, 0xDB342856, 0x190834C7, 0xFBEB7D8E,
0x251BED34, 0x0E9F2AAD, 0x256AB901, 0x0A5B7890,
0x9F124F09, 0xD84A9151, 0x427AF67A, 0x8059C9AA,
0x13EAB029, 0x3153CDF1, 0x262D405D, 0xA2105D87,
0x9C745F15, 0xD1613847, 0x294CE135, 0x20FB0F3C,
0x8424D8ED, 0x8F4201B6, 0x12CA1EA7, 0x2054B091,
0x463D8288, 0xC83253C3, 0x33EA314A, 0x9696DC92,
0xD041CE9A, 0xE5477160, 0xC7656BE8, 0x5179FE33,
0x1F4726F1, 0x5F393AF0, 0x26E2D004, 0x6D020245,
0x85FDF6D7, 0xB0237C56, 0xFF5FBD94, 0xA8B3F534
)
def mikro_softwareid_decode(software_id:str)->int:
assert(isinstance(software_id, str))
software_id = software_id.replace('-', '')
ret = 0
for i in reversed(range(len(software_id))):
ret *= len(SOFTWARE_ID_CHARACTER_TABLE)
ret += SOFTWARE_ID_CHARACTER_TABLE.index(ord(software_id[i]))
return ret
def mikro_softwareid_encode(id:int)->str:
assert(isinstance(id, int))
ret = ''
for i in range(8):
ret += chr(SOFTWARE_ID_CHARACTER_TABLE[id % 0x23])
id //= 0x23
if i == 3:
ret += '-'
return ret
def to32bits(v):
return (v + (1 << 32)) % (1 << 32)
def rotl(n, d):
return (n << d) | (n >> (32 - d))
def mikro_encode(s:bytes)->bytes:
s = list(struct.unpack('>' + 'I' * (len(s) // 4), s))
for i in reversed(range(16)):
s[(i+0) % 4] = to32bits(rotl(s[(i+3) % 4], MIKRO_SHA256_K[i*4+3] & 0x0F) ^ (s[(i+0) % 4] - s[(i+3) % 4]))
s[(i+3) % 4] = to32bits(s[(i+3) % 4] + s[(i+1) % 4] + MIKRO_SHA256_K[i*4+3])
s[(i+1) % 4] = to32bits(rotl(s[(i+2) % 4], MIKRO_SHA256_K[i*4+2] & 0x0F) ^ (s[(i+1) % 4] - s[(i+2) % 4]))
s[(i+0) % 4] = to32bits(s[(i+0) % 4] + s[(i+2) % 4] + MIKRO_SHA256_K[i*4+2])
s[(i+2) % 4] = to32bits(rotl(s[(i+1) % 4], MIKRO_SHA256_K[i*4+1] & 0x0F) ^ (s[(i+2) % 4] - s[(i+1) % 4]))
s[(i+1) % 4] = to32bits(s[(i+1) % 4] + s[(i+3) % 4] + MIKRO_SHA256_K[i*4+1])
s[(i+3) % 4] = to32bits(rotl(s[(i+0) % 4], MIKRO_SHA256_K[i*4+0] & 0x0F) ^ (s[(i+3) % 4] - s[(i+0) % 4]))
s[(i+2) % 4] = to32bits(s[(i+2) % 4] + s[(i+0) % 4] + MIKRO_SHA256_K[i*4+0])
encodedLicensePayload = b''
for x in s:
encodedLicensePayload += x.to_bytes(4, 'big')
return encodedLicensePayload
def mikro_decode(s:bytes)->bytes:
s = list(struct.unpack('>'+'I'*(len(s) // 4), s))
for i in range(16):
s[(i+2) % 4] = to32bits(s[(i+2) % 4] - s[(i+0) % 4] - MIKRO_SHA256_K[i*4+0])
s[(i+3) % 4] = to32bits((rotl(s[(i+0) % 4], MIKRO_SHA256_K[i*4+0] & 0x0F) ^ s[(i+3) % 4]) + s[(i+0) % 4])
s[(i+1) % 4] = to32bits(s[(i+1) % 4] - s[(i+3) % 4] - MIKRO_SHA256_K[i*4+1])
s[(i+2) % 4] = to32bits((rotl(s[(i+1) % 4], MIKRO_SHA256_K[i*4+1] & 0x0F) ^ s[(i+2) % 4]) + s[(i+1) % 4])
s[(i+0) % 4] = to32bits(s[(i+0) % 4] - s[(i+2) % 4] - MIKRO_SHA256_K[i*4+2])
s[(i+1) % 4] = to32bits((rotl(s[(i+2) % 4], MIKRO_SHA256_K[i*4+2] & 0x0F) ^ s[(i+1) % 4]) + s[(i+2) % 4])
s[(i+3) % 4] = to32bits(s[(i+3) % 4] - s[(i+1) % 4] - MIKRO_SHA256_K[i*4+3])
s[(i+0) % 4] = to32bits((rotl(s[(i+3) % 4], MIKRO_SHA256_K[i*4+3] & 0x0F) ^ s[(i+0) % 4]) + s[(i+3) % 4])
ret = b''
for x in s:
ret += x.to_bytes(4, 'big')
return ret
def mikro_base64_encode(data:bytes, pad = False)->str:
encoded = ''
left = 0
for i in range(0, len(data)):
if left == 0:
encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[data[i] & 0x3F])
left = 2
else:
if left == 6:
encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[data[i - 1] >> 2])
encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[data[i] & 0x3F])
left = 2
else:
index1 = data[i - 1] >> (8 - left)
index2 = data[i] << (left)
encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[(index1 | index2) & 0x3F])
left += 2
if left != 0:
encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[data[len(data) - 1] >> (8 - left)])
if pad:
for i in range(0, (4 - len(encoded) % 4) % 4):
encoded += '='
return encoded
def mikro_base64_decode(data:str)->bytes:
ret = b""
data = data.replace("=", "").encode()
left = 0
for i in range(0, len(data)):
if left == 0:
left = 6
else:
value1 = MIKRO_BASE64_CHARACTER_TABLE.index(data[i - 1]) >> (6 - left)
value2 = MIKRO_BASE64_CHARACTER_TABLE.index(data[i]) & (2 ** (8 - left) - 1)
value = value1 | (value2 << left)
ret += bytes([value])
left -= 2
return ret
class MikroSHA256(SHA256):
K = MIKRO_SHA256_K
INITIAL_STATE = SHA256.State(
0x5B653932, 0x7B145F8F, 0x71FFB291, 0x38EF925F,
0x03E1AAF9, 0x4A2057CC, 0x4CAF4DD9, 0x643CC9EA
)
def mikro_sha256(data:bytes)->bytes:
return MikroSHA256(data).digest()
def mikro_eddsa_sign(data:bytes,private_key:bytes)->bytes:
assert(isinstance(data, bytes))
assert(isinstance(private_key, bytes))
curve = getcurvebyname('Ed25519')
private_key = ECPrivateKey.eddsa_decode(curve,private_key)
return private_key.eddsa_sign(data).encode()
def mikro_eddsa_verify(data:bytes,signature:bytes,public_key:bytes):
assert(isinstance(data, bytes))
assert(isinstance(signature, bytes))
assert(isinstance(public_key, bytes))
curve = getcurvebyname('Ed25519')
public_key = ECPublicKey.eddsa_decode(curve,public_key)
signature = ECPrivateKey.EDDSASignature.decode(curve,signature)
return public_key.eddsa_verify(data,signature)
def mikro_kcdsa_sign(data:bytes,private_key:bytes)->bytes:
assert(isinstance(data, bytes))
assert(isinstance(private_key, bytes))
curve = getcurvebyname('Curve25519')
private_key:ECPrivateKey = ECPrivateKey(Tools.bytestoint_le(private_key), curve)
public_key:ECPublicKey = private_key.pubkey
while True:
nonce_secret = secure_rand_int_between(1, curve.n - 1)
nonce_point = nonce_secret * curve.G
nonce = int(nonce_point.x) % curve.n
nonce_hash = mikro_sha256(Tools.inttobytes_le(nonce,32))
data_hash = bytearray(mikro_sha256(data))
for i in range(16):
data_hash[8+i] ^= nonce_hash[i]
data_hash[0] &= 0xF8
data_hash[31] &= 0x7F
data_hash[31] |= 0x40
data_hash = Tools.bytestoint_le(data_hash)
signature = pow(private_key.scalar, -1, curve.n) * (nonce_secret - data_hash)
signature %= curve.n
if int((public_key.point * signature + curve.G * data_hash).x) == nonce:
return bytes(nonce_hash[:16]+Tools.inttobytes_le(signature,32))
def mikro_kcdsa_verify(data:bytes, signature:bytes, public_key:bytes)->bool:
assert(isinstance(data, bytes))
assert(isinstance(signature, bytes))
assert(isinstance(public_key, bytes))
curve = getcurvebyname('Curve25519')
#y^2 = x^3 + ax^2 + x
x = FieldElement(Tools.bytestoint_le(public_key), curve.p)
YY = ((x**3) + (curve.a * x**2) + x).sqrt()
public_keys = []
for y in YY:
public_keys += [AffineCurvePoint(x, int(y), curve)]
data_hash = bytearray(mikro_sha256(data))
nonce_hash = signature[:16]
signature = signature[16:]
for i in range(16):
data_hash[8+i] ^= nonce_hash[i]
data_hash[0] &= 0xF8
data_hash[31] &= 0x7F
data_hash[31] |= 0x40
data_hash = Tools.bytestoint_le(data_hash)
signature = Tools.bytestoint_le(signature)
for public_key in public_keys:
nonce = int((public_key * signature + curve.G * data_hash).x)
if mikro_sha256(Tools.inttobytes_le(nonce,32))[:len(nonce_hash)] == nonce_hash:
return True
return False

BIN
mikrotik.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

297
npk.py Normal file
View File

@ -0,0 +1,297 @@
import struct,zlib
from datetime import datetime
from dataclasses import dataclass
from enum import IntEnum
class NpkPartID(IntEnum):
NAME_INFO =0x01 # Package information: name, ver, etc.
DESCRIPTION =0x02 # Package description
DEPENDENCIES =0x03 # Package Dependencies
FILE_CONTAINER =0x04 # Files container zlib 1.2.3
INSTALL_SCRIPT =0x07 # Install script
UNINSTALL_SCRIPT =0x08 # Uninstall script
SIGNATURE =0x09 # Package signature
ARCHITECTURE =0x10 # Package architecture (e.g. i386)
PKG_CONFLICTS =0x11 # Package conflicts
PKG_INFO =0x12
FEATURES =0x13
PKG_FEATURES =0x14
SQUASHFS =0x15 # SquashFS
NULL_BLOCK =0X16
GIT_COMMIT =0x17 # Git commit
CHANNEL =0x18 # Release type (e.g. stable, bugfix)
HEADER =0x19
@dataclass
class NpkPartItem:
id: NpkPartID
data: bytes|object
class NpkNameInfo:
_format = '<16s4sI12s'
def __init__(self,name:str,version:str,build_time=datetime.now(),_unknow=b'\x00'*12):
self._name = name[:16].encode().ljust(16,b'\x00')
self._version = self.encode_version(version)
self._build_time = int(build_time.timestamp())
self._unknow = _unknow
def serialize(self)->bytes:
return struct.pack(self._format, self._name,self._version,self._build_time,self._unknow)
@staticmethod
def unserialize_from(data:bytes)->'NpkNameInfo':
assert len(data) == struct.calcsize(NpkNameInfo._format),'Invalid data length'
_name, _version,_build_time,_unknow = struct.unpack_from(NpkNameInfo._format,data)
return NpkNameInfo(_name.decode(),NpkNameInfo.decode_version(_version),datetime.fromtimestamp(_build_time),_unknow)
def __len__ (self)->int:
return struct.calcsize(self._format)
@property
def name(self)->str:
return self._name.decode().strip('\x00')
@name.setter
def name(self,value:str):
self._name = value[:16].encode().ljust(16,b'\x00')
@staticmethod
def decode_version(value:bytes):
revision,build,minor,major = struct.unpack_from('4B',value)
if build == 97:
build = 'alpha'
elif build == 98:
build = 'beta'
elif build == 99:
build = 'rc'
elif build == 102:
if revision & 0x80:
build = 'test'
revision &= 0x7f
else:
build = 'final'
else:
build = 'unknown'
return f'{major}.{minor}.{revision}.{build}'
@staticmethod
def encode_version(value:str):
s = value.split('.')
if 4 != len(s) and s[3] in [ 'alpha', 'beta', 'rc','final', 'test']:
raise ValueError('Invalid version string')
major = int(s[0])
minor = int(s[1])
revision = int(s[2])
if s[3] == 'alpha':
build = 97
elif s[3] == 'beta':
build = 98
elif s[3] == 'rc':
build = 99
elif s[3] == 'final':
build = 102
revision &= 0x7f
else: #'test'
build = 102
revision |= 0x80
return struct.pack('4B',revision,build,minor,major)
@property
def version(self)->str:
return self.decode_version(self._version)
@version.setter
def version(self,value:str = '7.15.1.final'):
self._version = self.encode_version(value)
@property
def build_time(self):
return datetime.fromtimestamp(self._build_time)
@build_time.setter
def build_time(self,value:datetime):
self._build_time = int(value.timestamp())
class NpkFileContainer:
_format = '<BB6sIBBBBIIIH'
@dataclass
class NpkFileItem:
perm: int
type: int
usr_or_grp: int
modify_time: int
revision: int
rc: int
minor: int
major: int
create_time: int
unknow: int
name: bytes
data: bytes
def __init__(self,items:list['NpkFileContainer.NpkFileItem']=None):
self._items= items
def serialize(self)->bytes:
compressed_data = b''
compressor = zlib.compressobj()
for item in self._items:
data = struct.pack(self._format, item.perm,item.type,item.usr_or_grp, item.modify_time,item.revision,item.rc,item.minor,item.major,item.create_time,item.unknow,len(item.data),len(item.name))
data += item.name + item.data
compressed_data += compressor.compress(data)
return compressed_data + compressor.flush()
@staticmethod
def unserialize_from(data:bytes):
items:list['NpkFileContainer.NpkFileItem'] = []
decompressed_data = zlib.decompress(data)
while len(decompressed_data):
offset = struct.calcsize(NpkFileContainer._format)
perm,type,usr_or_grp, modify_time,revision,rc,minor,major,create_time,unknow,data_size,name_size= struct.unpack_from(NpkFileContainer._format, decompressed_data)
name = decompressed_data[offset:offset+name_size]
data = decompressed_data[offset+name_size:offset+name_size+data_size]
items.append(NpkFileContainer.NpkFileItem(perm,type,usr_or_grp, modify_time,revision,rc,minor,major,create_time,unknow,name,data))
decompressed_data = decompressed_data[offset+name_size+data_size:]
return NpkFileContainer(items)
def __len__ (self)->int:
return len(self.serialize())
def __getitem__(self,index:int)->'NpkFileContainer.NpkFileItem':
return self._items[index]
def __iter__(self):
for item in self._items:
yield item
class NovaPackage:
NPK_MAGIC = 0xbad0f11e
def __init__(self,data:bytes=b''):
self._parts:list[NpkPartItem] = []
offset = 0
while offset < len(data):
part_id,part_size = struct.unpack_from('<HI',data,offset)
offset += 6
part_data = data[offset:offset+part_size]
offset += part_size
if part_id == NpkPartID.NAME_INFO:
self._parts.append(NpkPartItem(NpkPartID(part_id),NpkNameInfo.unserialize_from(part_data)))
# elif part_id == NpkPartID.FILE_CONTAINER:
# self._parts.append(NpkPartItem(NpkPartID(part_id),NpkFileContainer.unserialize_from(part_data)))
else:
self._parts.append(NpkPartItem(NpkPartID(part_id),part_data))
def get_digest(self,hash_fnc)->bytes:
for part in self._parts:
data_header = struct.pack('<HI',part.id.value,len(part.data))
if part.id == NpkPartID.HEADER:
continue
else:
hash_fnc.update(data_header)
if part.id == NpkPartID.SIGNATURE:
break
elif part.data:
if isinstance(part.data,bytes):
hash_fnc.update(part.data)
else:
hash_fnc.update(part.data.serialize())
return hash_fnc.digest()
def sign(self,kcdsa_private_key:bytes,eddsa_private_key:bytes):
import hashlib
from mikro import mikro_kcdsa_sign,mikro_eddsa_sign
self[NpkPartID.SIGNATURE].data = b'\0'*(20+48+64)
sha1_digest = self.get_digest(hashlib.new('SHA1'))
sha256_digest = self.get_digest(hashlib.new('SHA256'))
kcdsa_signature = mikro_kcdsa_sign(sha256_digest[:20],kcdsa_private_key)
eddsa_signature = mikro_eddsa_sign(sha256_digest,eddsa_private_key)
self[NpkPartID.SIGNATURE].data = sha1_digest + kcdsa_signature + eddsa_signature
def verify(self,kcdsa_public_key:bytes,eddsa_public_key:bytes):
import hashlib
from mikro import mikro_kcdsa_verify,mikro_eddsa_verify
sha1_digest = self.get_digest(hashlib.new('SHA1'))
sha256_digest = self.get_digest(hashlib.new('SHA256'))
signature = self[NpkPartID.SIGNATURE].data
if sha1_digest != signature[:20]:
return False
if not mikro_kcdsa_verify(sha256_digest[:20],signature[20:68],kcdsa_public_key):
return False
if not mikro_eddsa_verify(sha256_digest,signature[68:132],eddsa_public_key):
return False
return True
def __iter__(self):
for part in self._parts:
yield part
def __getitem__(self, id:NpkPartID):
for part in self._parts:
if part.id == id:
return part
part = NpkPartItem(id,b'')
self._parts.append(part)
return part
def save(self,file):
size = 0
for part in self._parts:
size += 6 + len(part.data)
with open(file,'wb') as f:
f.write(struct.pack('<II', NovaPackage.NPK_MAGIC, size))
for part in self._parts:
f.write(struct.pack('<HI',part.id.value ,len(part.data)))
if isinstance(part.data,bytes):
f.write(part.data)
else:
f.write(part.data.serialize())
@staticmethod
def load(file):
with open(file,'rb') as f:
data = f.read()
assert int.from_bytes(data[:4],'little') == NovaPackage.NPK_MAGIC, 'Invalid Nova Package Magic'
assert int.from_bytes(data[4:8],'little') == len(data) - 8, 'Invalid Nova Package Size'
return NovaPackage(data[8:])
def get_latest_version(channel:str) -> str:
import requests
response = requests.get(f'https://upgrade.mikrotik.com/routeros/NEWESTa7.{channel}')
return response.text.split(' ')[0]
def create_option_npk(npk_file:str,squashfs_file:str)->NovaPackage:
return option_npk
if __name__=='__main__':
import argparse,os
parser = argparse.ArgumentParser(description='nova package creator and editor')
subparsers = parser.add_subparsers(dest="command")
sign_parser = subparsers.add_parser('sign',help='sign npk file')
sign_parser.add_argument('input',type=str, help='Input file')
sign_parser.add_argument('output',type=str,help='Output file')
verify_parser = subparsers.add_parser('verify',help='Verify npk file')
verify_parser.add_argument('input',type=str, help='Input file')
create_option_parser = subparsers.add_parser('create',help='Create option.npk file')
create_option_parser.add_argument('npk_file',type=str,help='From npk file')
create_option_parser.add_argument('squashfs',type=str,help='option squashfs file')
create_option_parser.add_argument('output',type=str,help='Output file')
args = parser.parse_args()
if args.command =='sign':
print(f'Signing {args.input}')
npk = NovaPackage.load(args.input)
kcdsa_private_key = bytes.fromhex(os.environ['CUSTOM_LICENSE_PRIVATE_KEY'])
eddsa_private_key = bytes.fromhex(os.environ['CUSTOM_NPK_SIGN_PRIVATE_KEY'])
npk.sign(kcdsa_private_key,eddsa_private_key)
npk.save(args.output)
elif args.command == 'verify':
npk = NovaPackage.load(args.input)
print(f'Verifying {args.input} ',end="")
kcdsa_public_key = bytes.fromhex(os.environ['CUSTOM_LICENSE_PUBLIC_KEY'])
eddsa_public_key = bytes.fromhex(os.environ['CUSTOM_NPK_SIGN_PUBLIC_KEY'])
if npk.verify(kcdsa_public_key,eddsa_public_key):
print('Valid')
exit(0)
else:
print('Invalid')
exit(1)
elif args.command =='create':
print(f'Creating option.npk from {args.npk_file}')
kcdsa_private_key = bytes.fromhex(os.environ['CUSTOM_LICENSE_PRIVATE_KEY'])
eddsa_private_key = bytes.fromhex(os.environ['CUSTOM_NPK_SIGN_PRIVATE_KEY'])
option_npk = NovaPackage.load(args.npk_file)
option_npk[NpkPartID.NAME_INFO].data.name = 'option'
option_npk[NpkPartID.DESCRIPTION].data = b'option package has busybox ash'
option_npk[NpkPartID.NULL_BLOCK].data = b''
option_npk[NpkPartID.SQUASHFS].data = open(args.squashfs,'rb').read()
option_npk.sign(kcdsa_private_key,eddsa_private_key)
option_npk.save(args.output)
print(f'Created {args.output}')
else:
parser.print_help()

111
patch.py Normal file
View File

@ -0,0 +1,111 @@
import subprocess,lzma
import struct,os
from npk import NovaPackage,NpkPartID,NpkFileContainer
def patch_bzimage(data:bytes,key_dict:dict):
print(f'bzImage size : {len(data)}')
PE_TEXT_SECTION_OFFSET = 414
HEADER_PAYLOAD_OFFSET = 584
HEADER_PAYLOAD_LENGTH_OFFSET = HEADER_PAYLOAD_OFFSET + 4
text_section_raw_data = struct.unpack_from('<I',data,PE_TEXT_SECTION_OFFSET)[0]
payload_offset = text_section_raw_data +struct.unpack_from('<I',data,HEADER_PAYLOAD_OFFSET)[0]
payload_length = struct.unpack_from('<I',data,HEADER_PAYLOAD_LENGTH_OFFSET)[0]
payload_length = payload_length - 4 #last 4 bytes is uncompressed size(z_output_len)
print(f'vmlinux xz offset : {payload_offset}')
print(f'vmlinux xz size : {payload_length}')
z_output_len = struct.unpack_from('<I',data,payload_offset+payload_length)[0]
print(f'z_output_len : {z_output_len}')
vmlinux_xz = data[payload_offset:payload_offset+payload_length]
vmlinux = lzma.decompress(vmlinux_xz)
print(f'vmlinux size : {len(vmlinux)}')
assert z_output_len == len(vmlinux), 'vmlinux size is not equal to expected'
CPIO_HEADER_MAGIC = b'07070100'
CPIO_FOOTER_MAGIC = b'TRAILER!!!\x00\x00\x00\x00' #545241494C455221212100000000
cpio_offset1 = vmlinux.index(CPIO_HEADER_MAGIC)
initramfs = vmlinux[cpio_offset1:]
cpio_offset2 = initramfs.index(CPIO_FOOTER_MAGIC)+len(CPIO_FOOTER_MAGIC)
initramfs = initramfs[:cpio_offset2]
new_initramfs = initramfs
for old_public_key,new_public_key in key_dict.items():
if old_public_key in new_initramfs:
print(f'initramfs public key patched {old_public_key[:16].hex().upper()}...')
new_initramfs = new_initramfs.replace(old_public_key,new_public_key)
new_vmlinux = vmlinux.replace(initramfs,new_initramfs)
new_vmlinux_xz = lzma.compress(new_vmlinux,check=lzma.CHECK_CRC32,filters=[
{"id": lzma.FILTER_X86},
{"id": lzma.FILTER_LZMA2, "preset": 8,'dict_size': 32*1024*1024},
])
new_payload_length = len(new_vmlinux_xz)
print(f'new vmlinux xz size : {new_payload_length}')
assert new_payload_length <= payload_length , 'new vmlinux.xz size is too big'
new_payload_length = new_payload_length + 4 #last 4 bytes is uncompressed size(z_output_len)
new_data = bytearray(data)
struct.pack_into('<I',new_data,HEADER_PAYLOAD_LENGTH_OFFSET,new_payload_length)
vmlinux_xz += struct.pack('<I',z_output_len)
new_vmlinux_xz += struct.pack('<I',z_output_len)
new_vmlinux_xz = new_vmlinux_xz.ljust(len(vmlinux_xz),b'\0')
new_data = new_data.replace(vmlinux_xz,new_vmlinux_xz)
print(f'new bzImage size : {len(new_data)}')
return new_data
def run_shell_command(command):
process = subprocess.run(command, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return process.stdout, process.stderr
def patch_squashfs(path,key_dict):
for root, dirs, files in os.walk(path):
for file in files:
file = os.path.join(root,file)
if os.path.isfile(file):
data = open(file,'rb').read()
for old_public_key,new_public_key in key_dict.items():
if old_public_key in data:
print(f'{file} public key patched {old_public_key[:16].hex().upper()}...')
data = data.replace(old_public_key,new_public_key)
open(file,'wb').write(data)
def patch_system_npk(npk_file,key_dict):
npk = NovaPackage.load(npk_file)
file_container = NpkFileContainer.unserialize_from(npk[NpkPartID.FILE_CONTAINER].data)
for item in file_container:
if item.name == b'boot/EFI/BOOT/BOOTX64.EFI':
print(f'patch {item.name} ...')
item.data = patch_bzimage(item.data,key_dict)
open('linux','wb').write(item.data)
break
npk[NpkPartID.FILE_CONTAINER].data = file_container.serialize()
try:
squashfs_file = 'squashfs.sfs'
extract_dir = 'squashfs-root'
open(squashfs_file,'wb').write(npk[NpkPartID.SQUASHFS].data)
print(f"extract {squashfs_file} ...")
_, stderr = run_shell_command(f"unsquashfs -d {extract_dir} {squashfs_file}")
print(stderr.decode())
patch_squashfs(extract_dir,key_dict)
print(f"pack {extract_dir} ...")
run_shell_command(f"rm -f {squashfs_file}")
_, stderr = run_shell_command(f"mksquashfs {extract_dir} {squashfs_file} -quiet -comp xz -no-xattrs -b 256k")
print(stderr.decode())
except Exception as e:
print(e)
print(f"clean ...")
run_shell_command(f"rm -rf {extract_dir}")
npk[NpkPartID.SQUASHFS].data = open(squashfs_file,'rb').read()
run_shell_command(f"rm -f {squashfs_file}")
kcdsa_private_key = bytes.fromhex(os.environ['CUSTOM_LICENSE_PRIVATE_KEY'])
eddsa_private_key = bytes.fromhex(os.environ['CUSTOM_NPK_SIGN_PRIVATE_KEY'])
npk.sign(kcdsa_private_key,eddsa_private_key)
npk.save(npk_file)
if __name__ == '__main__':
import os,sys
key_dict = {
bytes.fromhex(os.environ['MIKRO_LICENSE_PUBLIC_KEY']):bytes.fromhex(os.environ['CUSTOM_LICENSE_PUBLIC_KEY']),
bytes.fromhex(os.environ['MIKRO_NPK_SIGN_PUBLIC_LKEY']):bytes.fromhex(os.environ['CUSTOM_NPK_SIGN_PUBLIC_KEY'])
}
if len(sys.argv) == 2:
print(f'patching {sys.argv[1]} ...')
patch_system_npk(sys.argv[1],key_dict)
else:
print('usage: python patch.py npk_file')

BIN
routeros.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

317
sha256.py Normal file
View File

@ -0,0 +1,317 @@
#!/usr/bin/env python
#
# Copyright (c) 2012 Dave Pifke.
#
# 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.
#
"""SHA256 (FIPS 180-3) implementation for experimentation."""
import binascii
import codecs
import collections
import struct
import sys
if sys.version > '3':
long = int
class SHA256(object):
"""
SHA256 (FIPS 180-3) implementation for experimentation.
This is an implementation of the hash function designed not for
efficiency, but for clarity and ability to experiment. The details
of the algorithm are abstracted out with subclassing in mind.
"""
# Container for the state registers between rounds:
State = collections.namedtuple('State', 'a b c d e f g h')
# From FIPS 180-3 section 5.3.3 (page 15):
INITIAL_STATE = State(
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
)
# From FIPS 180-3 section 4.2.2 (page 11):
K = (
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
)
# Abstract bitwise operations, which can be overridden to provide tracing
# or alternate implementations:
@staticmethod
def _sum_mod32(*args):
return sum(args) & 0xffffffff
@classmethod
def _xor(cls, *args):
if len(args) == 2:
return args[0] ^ args[1]
else:
return args[0] ^ cls._xor(*args[1:])
_and = staticmethod(lambda x, y: x & y)
_invert = staticmethod(lambda x: ~x)
# Operations defined by FIPS 180-3 section 3.2 (page 8):
_rrot = staticmethod(lambda x, n: ((x & 0xffffffff) >> n) | (x << (32 - n)) & 0xffffffff)
_shr = staticmethod(lambda x, n: (x & 0xffffffff) >> n)
# Operations defined by FIPS 180-3 section 4.1.2 (page 10):
_ch = classmethod(lambda cls, x, y, z: cls._xor(cls._and(x, y), cls._and(cls._invert(x), z)))
_maj = classmethod(lambda cls, x, y, z: cls._xor(cls._and(x, y), cls._and(x, z), cls._and(y, z)))
_S0 = classmethod(lambda cls, x: cls._xor(cls._rrot(x, 2), cls._rrot(x, 13), cls._rrot(x, 22)))
_S1 = classmethod(lambda cls, x: cls._xor(cls._rrot(x, 6), cls._rrot(x, 11), cls._rrot(x, 25)))
_s0 = classmethod(lambda cls, x: cls._xor(cls._rrot(x, 7), cls._rrot(x, 18), cls._shr(x, 3)))
_s1 = classmethod(lambda cls, x: cls._xor(cls._rrot(x, 17), cls._rrot(x, 19), cls._shr(x, 10)))
# Operations defined by FIPS 180-3 section 6.2.2 (page 22):
_T1 = classmethod(lambda cls, prev, w, k: cls._sum_mod32(cls._S1(prev.e), cls._ch(prev.e, prev.f, prev.g), prev.h, w, k))
_T2 = classmethod(lambda cls, prev: cls._sum_mod32(cls._S0(prev.a), cls._maj(prev.a, prev.b, prev.c)))
@classmethod
def _round(cls, number, w, prev=INITIAL_STATE):
"""
Performs one round of SHA256 message transformation, returning the new
message state. See FIPS 180-3 section 6.2.2 step 3 (pages 21-22).
:param number:
The round number.
:param w:
The expanded word of the input for this round.
:param prev:
Named tuple containing the working state from the previous round.
"""
t1 = cls._T1(prev, w, cls.K[number % 64])
return cls.State(
a=cls._sum_mod32(t1, cls._T2(prev)),
b=prev.a,
c=prev.b,
d=prev.c,
e=cls._sum_mod32(prev.d, t1),
f=prev.e,
g=prev.f,
h=prev.g
)
@classmethod
def _finalize(cls, state, initial_state=INITIAL_STATE):
"""
Returns the intermediate state after the final round for a given block
is complete. See FIPS 180-3 section 6.2.2 step 4 (page 22).
:param state:
The digest state after the final round.
:param initial_state:
The digest state from before the first round.
"""
return cls.State(
a=cls._sum_mod32(state.a, initial_state.a),
b=cls._sum_mod32(state.b, initial_state.b),
c=cls._sum_mod32(state.c, initial_state.c),
d=cls._sum_mod32(state.d, initial_state.d),
e=cls._sum_mod32(state.e, initial_state.e),
f=cls._sum_mod32(state.f, initial_state.f),
g=cls._sum_mod32(state.g, initial_state.g),
h=cls._sum_mod32(state.h, initial_state.h)
)
@classmethod
def _expand_message(cls, message):
"""
Returns a list of 64 32-bit words based upon 16 32-bit words from the
message block being hashed. See FIPS 180-3 section 6.2.2 step 1
(page 21).
:param message:
Array of 16 32-bit values (512 bits total).
"""
assert len(message) == 16, '_expand_message() got %d words, expected 16' % len(message)
w = list(message)
for i in range(16, 64):
w.append(cls._sum_mod32(w[i - 16], cls._s0(w[i - 15]), w[i - 7], cls._s1(w[i - 2])))
return w
@classmethod
def _process_block(cls, message, state=INITIAL_STATE, round_offset=0):
"""
Processes a block of message data, returning the new digest state
(the intermediate hash value). See FIPS 180-3 section 6.2.2 (pages
21 and 22).
:param message:
Byte string of length 64 containing the block data to hash.
:param state:
The digest state from the previous block.
:param round_offset:
The _round() method can be overridden to report intermediate hash
values, in which case it's useful to know how many rounds came
before. This argument allows the caller to specify as much.
"""
assert len(message) == 64, '_process_block() got %d bytes, expected 64' % len(message)
assert not round_offset % 64, 'round_offset should be a multiple of 64'
w = cls._expand_message(struct.unpack('>LLLLLLLLLLLLLLLL', message))
midstate = state
for i in range(64):
midstate = cls._round(round_offset + i, w[i], midstate)
return cls._finalize(midstate, state)
@classmethod
def _pad_message(cls, message, length):
"""
Returns a list containing the final 1 or 2 message blocks, which
include the message padding per FIPS 180-3 section 5.1.1 (page 13).
:param message:
Byte string containing the final block data to hash. Should be
less than a full block's worth (63 bytes or less).
:param length:
Length of the message, in bits.
"""
assert len(message) < 64, 'Input to _pad_message() must be less than 512 bits'
if len(message) <= 55:
# Append trailing 1 bit, then padding, then length
return [b''.join((
message,
b'\x80',
b'\x00' * (55 - len(message)),
struct.pack('>LL', length >> 32, length & 0xffffffff),
))]
else:
# Not enough room to append length, return two blocks:
return [
# First is trailing 1 bit, then padding
b''.join((
message,
b'\x80',
b'\x00' * (63 - len(message)),
)),
# Next is more padding, then length
b''.join((
b'\x00' * 56,
struct.pack('>LL', length >> 32, length & 0xffffffff),
)),
]
def __init__(self, message=b'', round_offset=0):
"""
Constructor.
:param message:
Initial data to pass to update().
:param round_offset:
The _round() method can be overridden to report intermediate hash
values, in which case it's useful to know how many rounds came
before. For applications that perform double-hashing, you can
specify the number of rounds from the previous hash instance
using this parameter.
"""
self.state = self.INITIAL_STATE
self.length = long(0)
self.buffer = b''
self.round_offset = round_offset
self.update(message)
def update(self, message):
"""
Updates the hash with the contents of *message*.
Hashing uses 512-bit blocks, so the message is buffered until there's
enough data to process a complete block. When digest() is called,
any remaining data in the buffer will be padded and digested.
:param message:
A byte string to digest.
"""
message = bytes(message)
self.length += len(message) * 8
self.buffer = b''.join((self.buffer, message))
while len(self.buffer) >= 64:
self.state = self._process_block(self.buffer[:64], self.state, self.round_offset)
self.buffer = self.buffer[64:]
self.round_offset += 64
def digest(self):
"""
Returns the SHA256 digest of the message.
The hash is based on all data passed thus far via the constructor and
update(). Any buffered data will be processed (along with the
terminating length), however the internal state is not modified. This
means that update() can safely be used again after digest().
"""
final_state = self.state
for block in self._pad_message(self.buffer, self.length):
final_state = self._process_block(block, final_state, self.round_offset)
return struct.pack('>LLLLLLLL', *final_state)
def hexdigest(self):
"""Like digest(), but returns a hexadecimal string."""
return binascii.hexlify(self.digest())

188
toyecc/ASN1.py Normal file
View File

@ -0,0 +1,188 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
try:
from pyasn1.type import univ, namedtype, tag
import pyasn1.codec.ber.decoder
class ECPVer(univ.Integer):
"""RFC 3279: Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
ECPVer ::= INTEGER {ecpVer1(1)}
"""
pass
class FieldElement(univ.OctetString):
"""RFC 3279: Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
FieldElement ::= OCTET STRING
"""
pass
class ECPoint(univ.OctetString):
"""RFC 3279: Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
ECPoint ::= OCTET STRING
"""
pass
class Curve(univ.Sequence):
"""RFC 3279: Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
Curve ::= SEQUENCE {
a FieldElement,
b FieldElement,
seed BIT STRING OPTIONAL
}
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType("a", FieldElement()),
namedtype.NamedType("b", FieldElement()),
namedtype.OptionalNamedType("seed", univ.BitString()),
)
class FieldID(univ.Sequence):
"""RFC 3279: Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
FieldID ::= SEQUENCE {
fieldType OBJECT IDENTIFIER,
parameters ANY DEFINED BY fieldType
}
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType("fieldType", univ.ObjectIdentifier()),
namedtype.NamedType("parameters", univ.Any()),
)
class SpecifiedECDomain(univ.Sequence):
"""RFC 3279: Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
ECParameters ::= SEQUENCE {
version ECPVer, -- version is always 1
fieldID FieldID, -- identifies the finite field over which the curve is defined
curve Curve, -- coefficients a and b of the elliptic curve
base ECPoint, -- specifies the base point P on the elliptic curve
order INTEGER, -- the order n of the base point
cofactor INTEGER OPTIONAL -- The integer h = #E(Fq)/n
}
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType("version", ECPVer()),
namedtype.NamedType("fieldID", FieldID()),
namedtype.NamedType("curve", Curve()),
namedtype.NamedType("base", ECPoint()),
namedtype.NamedType("order", univ.Integer()),
namedtype.OptionalNamedType("cofactor", univ.Integer()),
)
class ECParameters(univ.Choice):
"""RFC 5480: Elliptic Curve Cryptography Subject Public Key Information
ECParameters ::= CHOICE {
namedCurve OBJECT IDENTIFIER
implicitCurve NULL
specifiedCurve SpecifiedECDomain
}
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType("namedCurve", univ.ObjectIdentifier()),
namedtype.NamedType("implicitCurve", univ.Null()),
namedtype.NamedType("specifiedCurve", SpecifiedECDomain()),
)
class ECPrivateKey(univ.Sequence):
"""RFC 5915: Elliptic Curve Private Key Structure
ECPrivateKey ::= SEQUENCE {
version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
privateKey OCTET STRING,
parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
publicKey [1] BIT STRING OPTIONAL
}
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType("version", univ.Integer()),
namedtype.NamedType("privateKey", univ.OctetString()),
namedtype.OptionalNamedType("parameters", ECParameters().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
namedtype.OptionalNamedType("publicKey", univ.BitString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
)
class AlgorithmIdentifier(univ.Sequence):
"""RFC 5480: Elliptic Curve Cryptography Subject Public Key Information
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType("algorithm", univ.ObjectIdentifier()),
namedtype.NamedType("parameters", ECParameters()),
)
class ECPublicKey(univ.Sequence):
"""RFC 5480: Elliptic Curve Cryptography Subject Public Key Information
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING
}
"""
componentType = namedtype.NamedTypes(
namedtype.NamedType("algorithm", AlgorithmIdentifier()),
namedtype.NamedType("subjectPublicKey", univ.BitString()),
)
class FieldFPParameters(univ.Integer):
"""For F_P fields, the field parameters is just the integer P."""
pass
__have_asn1 = True
except ImportError:
__have_asn1 = False
def have_asn1_support():
return __have_asn1
def __assert_asn1_support():
if not have_asn1_support():
raise Exception("ASN.1 support is required, but the pyasn1 library could not be imported. Functionality not available.")
def parse_asn1_field_params_fp(derdata):
"""Parse an ASN.1 DER encoded field parameter for fields in F_P."""
__assert_asn1_support()
(parsed, tail) = pyasn1.codec.ber.decoder.decode(derdata, asn1Spec = FieldFPParameters())
return parsed
def parse_asn1_public_key(derdata):
"""Parse an ASN.1 DER encoded EC public key."""
__assert_asn1_support()
(parsed, tail) = pyasn1.codec.ber.decoder.decode(derdata, asn1Spec = ECPublicKey())
return parsed
def parse_asn1_private_key(derdata):
"""Parse an ASN.1 DER encoded EC private key."""
__assert_asn1_support()
(parsed, tail) = pyasn1.codec.ber.decoder.decode(derdata, asn1Spec = ECPrivateKey())
return parsed

132
toyecc/AffineCurvePoint.py Normal file
View File

@ -0,0 +1,132 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
import math
from .FieldElement import FieldElement
from .PointOps import PointOpEDDSAEncoding, PointOpCurveConversion, PointOpNaiveOrderCalculation, PointOpSerialization, PointOpScalarMultiplicationXOnly
class AffineCurvePoint(PointOpEDDSAEncoding, PointOpCurveConversion, PointOpNaiveOrderCalculation, PointOpSerialization, PointOpScalarMultiplicationXOnly):
"""Represents a point on a curve in affine (x, y) representation."""
def __init__(self, x, y, curve):
"""Generate a curve point (x, y) on the curve 'curve'. x and y have to
be integers. If the neutral element of the group O (for some curves,
this is a point at infinity) should be created, use the static method
'neutral', since representations of O differ on various curves (e.g. in
short Weierstrass curves, they have no explicit notation in affine
space while on twisted Edwards curves they do."""
# Either x and y are None (Point at Infty) or both are defined
assert(((x is None) and (y is None)) or ((x is not None) and (y is not None)))
assert((x is None) or isinstance(x, int))
assert((y is None) or isinstance(y, int))
if x is None:
# Point at infinity
self._x = None
self._y = None
else:
self._x = FieldElement(x, curve.p)
self._y = FieldElement(y, curve.p)
self._curve = curve
@staticmethod
def neutral(curve):
"""Returns the neutral element of the curve group."""
return curve.neutral()
@property
def is_neutral(self):
"""Indicates if the point is the neutral element O of the curve (point
at infinity for some curves)."""
return self.curve.is_neutral(self)
@property
def x(self):
"""Affine X component of the point, field element of p."""
return self._x
@property
def y(self):
"""Affine Y component of the point, field element of p."""
return self._y
@property
def curve(self):
"""Curve that the point is located on."""
return self._curve
def __add__(self, other):
"""Returns the point addition."""
assert(isinstance(other, AffineCurvePoint))
return self.curve.point_addition(self, other)
def __rmul__(self, other):
return self * other
def __neg__(self):
"""Returns the conjugated point."""
return self.curve.point_conjugate(self)
def __mul__(self, scalar):
"""Returns the scalar point multiplication. The scalar needs to be an
integer value."""
assert(isinstance(scalar, int))
assert(scalar >= 0)
result = self.curve.neutral()
n = self
if scalar > 0:
for bit in range(scalar.bit_length()):
if (scalar & (1 << bit)):
result = result + n
n = n + n
#assert(result.oncurve())
return result
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return hash((self.x, self.y))
def oncurve(self):
"""Indicates if the given point is satisfying the curve equation (i.e.
if it is a point on the curve)."""
return self.curve.oncurve(self)
def compress(self):
"""Returns the compressed point format (if this is possible on the
given curve)."""
return self.curve.compress(self)
def __repr__(self):
return str(self)
def __str__(self):
if self.is_neutral:
return "(neutral)"
else:
return "(0x%x, 0x%x)" % (int(self.x), int(self.y))

59
toyecc/CRT.py Normal file
View File

@ -0,0 +1,59 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
from .FieldElement import FieldElement
class CRT():
"""Implements the Chinese Remainder Theorem algorithm where a number of
modular congruences are given that all need to be satisfied."""
def __init__(self):
self._moduli = { }
def add(self, value, modulus):
"""Adds a value that shall be returned when the result is taken modulo
the given modulus."""
assert(modulus not in self._moduli)
assert(isinstance(value, int))
assert(isinstance(modulus, int))
self._moduli[modulus] = value
return self
def solve(self):
"""Solve the Chinese Remainder Theorem for the given values and
moduli."""
# Calculate product of all moduli
product = 1
for modulus in self._moduli.keys():
product *= modulus
# Then determine the solution
solution = 0
for modulus in self._moduli.keys():
if self._moduli[modulus] == 0:
continue
rem_product = product // modulus
one_value = int(FieldElement(rem_product, modulus).inverse())
solution += rem_product * one_value * self._moduli[modulus]
return solution % product

842
toyecc/CurveDB.py Normal file
View File

@ -0,0 +1,842 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
import enum
import collections
from .ShortWeierstrassCurve import ShortWeierstrassCurve
from .MontgomeryCurve import MontgomeryCurve
from .TwistedEdwardsCurve import TwistedEdwardsCurve
from .Singleton import singleton
from .FieldElement import FieldElement
from .Exceptions import DuplicateCurveException, NoSuchCurveException, UnsupportedFieldException
from .ASN1 import parse_asn1_field_params_fp
from .AffineCurvePoint import AffineCurvePoint
from .CurveQuirks import CurveQuirkEdDSASetPrivateKeyMSB, CurveQuirkEdDSAEnsurePrimeOrderSubgroup, CurveQuirkSigningHashFunction
from . import Tools
@singleton
class CurveDB(object):
def __init__(self):
self._entries = { }
self._primary_names = set()
self._taken_names = set()
def _checknames(self, curvenames):
if len(curvenames & self._taken_names) > 0:
taken_names = ", ".join(sorted(list(curvenames & self._taken_names)))
raise DuplicateCurveException("Curve(s) named %s already registered in curve DB." % (taken_names))
def register(self, entry):
"""Registers a curve in the curve database."""
all_names = set(name.lower() for name in entry.all_aliases)
self._checknames(all_names)
self._taken_names |= all_names
self._primary_names.add(entry.name)
self._entries[entry.primary_name.lower()] = entry
for aliasname in entry.aliases:
clone = entry.clone(secondary_name = aliasname)
self._entries[aliasname.lower()] = clone
def curvenames(self):
"""Returns the primary names of all curves in the DB."""
return (curve.name for curve in self._entries.values() if (curve.is_aka is False))
def allcurvenames(self):
"""Returns all names of all curves in the DB. This includes duplicate
AKAs such as secp224r1 which is also known as wap-wsg-idm-ecid-wtls12
albeit under a different OID."""
return (curve.name for curve in self._entries.values())
def find_duplicate_curves(self):
"""Returns curves in which the domain parameters (including the
coordinates of the generator point G) are identical. This can happen if
identical curves are registered under the same name."""
params = collections.defaultdict(list)
for curve in self:
params[tuple(sorted(curve.domain_params))].append(curve.name)
return [ curves for (param, curves) in params.items() if (len(curves) > 1) ]
def getentry(self, name):
"""Returns a specific curve entry by its case-insensitive name."""
name = name.lower()
if name not in self._entries:
raise KeyError("Curve named '%s' is not known in curve database." % (name))
return self._entries[name]
def get_curve_from_asn1(self, asn1):
"""This function will take a parsed ASN.1 ECParameters class as input
and try to return the curve specified within. If the ECParameters
specify a named curve by its's OID then a lookup is performed against
the curve database and that named curve returned on success if
non-ambiguous. If the parameters are exclicitly stated, then an unnamed
ShortWeierstrassCurve is constructed."""
if asn1["namedCurve"] is not None:
# Curve is encoded as OID, look up from curve DB
curve_oid = str(asn1["namedCurve"])
entries = [ entry for entry in self if (entry.oid == curve_oid) ]
if len(entries) == 0:
raise NoSuchCurveException("Trying to load curve with OID %s from curve DB, but no such curve is present in database." % (curve_oid))
elif len(entries) > 1:
raise Exception("Trying to load curve with OID %s from curve DB, but found %d curves (refuse to guess in the face of ambiguity)." % (curve_oid, len(entries)))
curve = entries[0]()
elif asn1["specifiedCurve"] is not None:
field_type_oid = str(asn1["specifiedCurve"]["fieldID"]["fieldType"])
if field_type_oid == "1.2.840.10045.1.1":
# F_P curve is encoded in explicit form
p = int(parse_asn1_field_params_fp(asn1["specifiedCurve"]["fieldID"]["parameters"]))
a = Tools.bytestoint(asn1["specifiedCurve"]["curve"]["a"])
b = Tools.bytestoint(asn1["specifiedCurve"]["curve"]["b"])
G = bytes(asn1["specifiedCurve"]["base"])
(Gx, Gy) = AffineCurvePoint.deserialize_uncompressed(G)
n = int(asn1["specifiedCurve"]["order"])
h = int(asn1["specifiedCurve"]["cofactor"])
curve = ShortWeierstrassCurve(p = p, a = a, b = b, n = n, h = h, Gx = Gx, Gy = Gy)
else:
# Maybe F_2^N curve or some other, unsupported type
raise UnsupportedFieldException("Only supports elliptic curves in F_P are supported, but the requested field type OID was %s." % (field_type_oid))
else:
raise NoSuchCurveException("Cannot load implicit curve.")
return curve
def __iter__(self):
"""Iterates over the curve DB entries."""
for name in self.curvenames():
yield self._entries[name.lower()]
def __getitem__(self, name):
"""Returns a curve (not a curve DB entry) by its name."""
return self.getentry(name)()
def __str__(self):
return "CurveDB<%d unique curves, %d total>" % (len(self._primary_names), len(self._entries))
class _CurveDBEntry(object):
def __init__(self, primary_name, curve_class, domain_params, **kwargs):
allowed_kwargs = set(("oid", "alt_oids", "aliases", "origin", "secure", "quirks"))
wrong_args = kwargs.keys() - allowed_kwargs
if len(wrong_args) > 0:
raise Exception("Illegal keyword arguments: %s" % (", ".join(sorted(wrong_args))))
assert(primary_name is not None)
self._primary_name = primary_name
self._secondary_name = None
self._curve_class = curve_class
self._domain_params = domain_params
self._oid = kwargs.get("oid")
self._alt_oids = kwargs.get("alt_oids")
self._aliases = kwargs.get("aliases")
self._origin = kwargs.get("origin")
self._secure = kwargs.get("secure", True)
self._quirks = kwargs.get("quirks", [ ])
self._instance = None
def clone(self, secondary_name = None):
clone = _CurveDBEntry(primary_name = self._primary_name, curve_class = self._curve_class, domain_params = self._domain_params, oid = self._oid, alt_oids = self._alt_oids, aliases = self._aliases, origin = self._origin, secure = self._secure)
clone._instance = self._instance
clone._secondary_name = secondary_name
return clone
@property
def is_aka(self):
"""Returns if this curve entry is an AKA ('also known as') for a
different curve (but maybe with a different OID). Example: prime192v1
and secp192r1 refer to identical curves, but 'prime192v1' is the
internally considered primary name while 'secp192r1' is considered to
be an AKA."""
return self._secondary_name is not None
@property
def primary_name(self):
return self._primary_name
@property
def name(self):
if self._secondary_name is not None:
return self._secondary_name
else:
return self._primary_name
@property
def fieldsize_bits(self):
return self._domain_params["p"].bit_length()
@property
def secure(self):
return self._secure
@property
def origin(self):
return self._origin
@property
def bits_security_estimate(self):
if not self.secure:
return 0
else:
# Require instanciation of the class
self()
return self._instance.security_bit_estimate
def get_alternative_oid(self, name):
"""Returns the alternative OID if it has one."""
if self._alt_oids is not None:
return self._alt_oids.get(name)
@property
def oid(self):
if (self._alt_oids is not None) and (self.name in self._alt_oids):
return self._alt_oids[self.name]
else:
return self._oid
@property
def aliases(self):
if self._aliases is not None:
yield from self._aliases
@property
def all_aliases(self):
yield self._primary_name
yield from self.aliases
@property
def prettyname(self):
if self._instance is None:
return self._curve_class.pretty_name
else:
return self._instance.prettyname
@property
def domain_params(self):
if self._instance is None:
return dict(self._domain_params)
else:
return self._instance.domainparamdict
@property
def prettytitle(self):
return "%d bit %s Curve" % (self.fieldsize_bits, self.prettyname)
def dump(self, domain = False):
print("%s: %s" % (self.name, self.prettytitle))
if self._aliases is not None:
print("Aliases: %s" % (", ".join(sorted(list(self._aliases)))))
if self._oid is not None:
print("OID : %s" % (self._oid))
if domain:
print("Domain parameters:")
for (key, value) in sorted(self.domain_params.items()):
if isinstance(value, FieldElement):
value = value.sigint()
print(" %-10s %s" % (key, value))
def __call__(self):
"""Instanciate the curve."""
if self._instance is None:
# Instanciate actual curve
params = self._domain_params
params["name"] = self.name
params["quirks"] = self._quirks
self._instance = self._curve_class(**params)
return self._instance
def __str__(self):
if self._secondary_name is not None:
return "CurveDBEntry<%s AKA %s>" % (self.primary_name, self._secondary_name)
else:
return "CurveDBEntry<%s>" % (self.name)
cdb = CurveDB()
cdb.register(_CurveDBEntry("brainpoolP160r1", ShortWeierstrassCurve, {
"a": 0x340e7be2a280eb74e2be61bada745d97e8f7c300,
"b": 0x1e589a8595423412134faa2dbdec95c8d8675e58,
"p": 0xe95e4a5f737059dc60dfc7ad95b3d8139515620f,
"n": 0xe95e4a5f737059dc60df5991d45029409e60fc09,
"h": 1,
"Gx": 0xbed5af16ea3f6a4f62938c4631eb5af7bdbcdbc3,
"Gy": 0x1667cb477a1a8ec338f94741669c976316da6321,
}, oid = "1.3.36.3.3.2.8.1.1.1", origin = "ECC Brainpool"))
cdb.register(_CurveDBEntry("brainpoolP160t1", ShortWeierstrassCurve, {
"a": 0xe95e4a5f737059dc60dfc7ad95b3d8139515620c,
"b": 0x7a556b6dae535b7b51ed2c4d7daa7a0b5c55f380,
"p": 0xe95e4a5f737059dc60dfc7ad95b3d8139515620f,
"n": 0xe95e4a5f737059dc60df5991d45029409e60fc09,
"h": 1,
"Gx": 0xb199b13b9b34efc1397e64baeb05acc265ff2378,
"Gy": 0xadd6718b7c7c1961f0991b842443772152c9e0ad,
}, oid = "1.3.36.3.3.2.8.1.1.2", origin = "ECC Brainpool"))
cdb.register(_CurveDBEntry("brainpoolP192r1", ShortWeierstrassCurve, {
"a": 0x6a91174076b1e0e19c39c031fe8685c1cae040e5c69a28ef,
"b": 0x469a28ef7c28cca3dc721d044f4496bcca7ef4146fbf25c9,
"p": 0xc302f41d932a36cda7a3463093d18db78fce476de1a86297,
"n": 0xc302f41d932a36cda7a3462f9e9e916b5be8f1029ac4acc1,
"h": 1,
"Gx": 0xc0a0647eaab6a48753b033c56cb0f0900a2f5c4853375fd6,
"Gy": 0x14b690866abd5bb88b5f4828c1490002e6773fa2fa299b8f,
}, oid = "1.3.36.3.3.2.8.1.1.3", origin = "ECC Brainpool"))
cdb.register(_CurveDBEntry("brainpoolP192t1", ShortWeierstrassCurve, {
"a": 0xc302f41d932a36cda7a3463093d18db78fce476de1a86294,
"b": 0x13d56ffaec78681e68f9deb43b35bec2fb68542e27897b79,
"p": 0xc302f41d932a36cda7a3463093d18db78fce476de1a86297,
"n": 0xc302f41d932a36cda7a3462f9e9e916b5be8f1029ac4acc1,
"h": 1,
"Gx": 0x3ae9e58c82f63c30282e1fe7bbf43fa72c446af6f4618129,
"Gy": 0x97e2c5667c2223a902ab5ca449d0084b7e5b3de7ccc01c9,
}, oid = "1.3.36.3.3.2.8.1.1.4", origin = "ECC Brainpool"))
cdb.register(_CurveDBEntry("brainpoolP224r1", ShortWeierstrassCurve, {
"a": 0x68a5e62ca9ce6c1c299803a6c1530b514e182ad8b0042a59cad29f43,
"b": 0x2580f63ccfe44138870713b1a92369e33e2135d266dbb372386c400b,
"p": 0xd7c134aa264366862a18302575d1d787b09f075797da89f57ec8c0ff,
"n": 0xd7c134aa264366862a18302575d0fb98d116bc4b6ddebca3a5a7939f,
"h": 1,
"Gx": 0xd9029ad2c7e5cf4340823b2a87dc68c9e4ce3174c1e6efdee12c07d,
"Gy": 0x58aa56f772c0726f24c6b89e4ecdac24354b9e99caa3f6d3761402cd,
}, oid = "1.3.36.3.3.2.8.1.1.5", origin = "ECC Brainpool"))
cdb.register(_CurveDBEntry("brainpoolP224t1", ShortWeierstrassCurve, {
"a": 0xd7c134aa264366862a18302575d1d787b09f075797da89f57ec8c0fc,
"b": 0x4b337d934104cd7bef271bf60ced1ed20da14c08b3bb64f18a60888d,
"p": 0xd7c134aa264366862a18302575d1d787b09f075797da89f57ec8c0ff,
"n": 0xd7c134aa264366862a18302575d0fb98d116bc4b6ddebca3a5a7939f,
"h": 1,
"Gx": 0x6ab1e344ce25ff3896424e7ffe14762ecb49f8928ac0c76029b4d580,
"Gy": 0x374e9f5143e568cd23f3f4d7c0d4b1e41c8cc0d1c6abd5f1a46db4c,
}, oid = "1.3.36.3.3.2.8.1.1.6", origin = "ECC Brainpool"))
cdb.register(_CurveDBEntry("brainpoolP256r1", ShortWeierstrassCurve, {
"a": 0x7d5a0975fc2c3057eef67530417affe7fb8055c126dc5c6ce94a4b44f330b5d9,
"b": 0x26dc5c6ce94a4b44f330b5d9bbd77cbf958416295cf7e1ce6bccdc18ff8c07b6,
"p": 0xa9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377,
"n": 0xa9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a7,
"h": 1,
"Gx": 0x8bd2aeb9cb7e57cb2c4b482ffc81b7afb9de27e1e3bd23c23a4453bd9ace3262,
"Gy": 0x547ef835c3dac4fd97f8461a14611dc9c27745132ded8e545c1d54c72f046997,
}, oid = "1.3.36.3.3.2.8.1.1.7", origin = "ECC Brainpool"))
cdb.register(_CurveDBEntry("brainpoolP256t1", ShortWeierstrassCurve, {
"a": 0xa9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5374,
"b": 0x662c61c430d84ea4fe66a7733d0b76b7bf93ebc4af2f49256ae58101fee92b04,
"p": 0xa9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377,
"n": 0xa9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a7,
"h": 1,
"Gx": 0xa3e8eb3cc1cfe7b7732213b23a656149afa142c47aafbc2b79a191562e1305f4,
"Gy": 0x2d996c823439c56d7f7b22e14644417e69bcb6de39d027001dabe8f35b25c9be,
}, oid = "1.3.36.3.3.2.8.1.1.8", origin = "ECC Brainpool"))
cdb.register(_CurveDBEntry("brainpoolP320r1", ShortWeierstrassCurve, {
"a": 0x3ee30b568fbab0f883ccebd46d3f3bb8a2a73513f5eb79da66190eb085ffa9f492f375a97d860eb4,
"b": 0x520883949dfdbc42d3ad198640688a6fe13f41349554b49acc31dccd884539816f5eb4ac8fb1f1a6,
"p": 0xd35e472036bc4fb7e13c785ed201e065f98fcfa6f6f40def4f92b9ec7893ec28fcd412b1f1b32e27,
"n": 0xd35e472036bc4fb7e13c785ed201e065f98fcfa5b68f12a32d482ec7ee8658e98691555b44c59311,
"h": 1,
"Gx": 0x43bd7e9afb53d8b85289bcc48ee5bfe6f20137d10a087eb6e7871e2a10a599c710af8d0d39e20611,
"Gy": 0x14fdd05545ec1cc8ab4093247f77275e0743ffed117182eaa9c77877aaac6ac7d35245d1692e8ee1,
}, oid = "1.3.36.3.3.2.8.1.1.9", origin = "ECC Brainpool"))
cdb.register(_CurveDBEntry("brainpoolP320t1", ShortWeierstrassCurve, {
"a": 0xd35e472036bc4fb7e13c785ed201e065f98fcfa6f6f40def4f92b9ec7893ec28fcd412b1f1b32e24,
"b": 0xa7f561e038eb1ed560b3d147db782013064c19f27ed27c6780aaf77fb8a547ceb5b4fef422340353,
"p": 0xd35e472036bc4fb7e13c785ed201e065f98fcfa6f6f40def4f92b9ec7893ec28fcd412b1f1b32e27,
"n": 0xd35e472036bc4fb7e13c785ed201e065f98fcfa5b68f12a32d482ec7ee8658e98691555b44c59311,
"h": 1,
"Gx": 0x925be9fb01afc6fb4d3e7d4990010f813408ab106c4f09cb7ee07868cc136fff3357f624a21bed52,
"Gy": 0x63ba3a7a27483ebf6671dbef7abb30ebee084e58a0b077ad42a5a0989d1ee71b1b9bc0455fb0d2c3,
}, oid = "1.3.36.3.3.2.8.1.1.10", origin = "ECC Brainpool"))
cdb.register(_CurveDBEntry("brainpoolP384r1", ShortWeierstrassCurve, {
"a": 0x7bc382c63d8c150c3c72080ace05afa0c2bea28e4fb22787139165efba91f90f8aa5814a503ad4eb04a8c7dd22ce2826,
"b": 0x4a8c7dd22ce28268b39b55416f0447c2fb77de107dcd2a62e880ea53eeb62d57cb4390295dbc9943ab78696fa504c11,
"p": 0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec53,
"n": 0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202e9046565,
"h": 1,
"Gx": 0x1d1c64f068cf45ffa2a63a81b7c13f6b8847a3e77ef14fe3db7fcafe0cbd10e8e826e03436d646aaef87b2e247d4af1e,
"Gy": 0x8abe1d7520f9c2a45cb1eb8e95cfd55262b70b29feec5864e19c054ff99129280e4646217791811142820341263c5315,
}, oid = "1.3.36.3.3.2.8.1.1.11", origin = "ECC Brainpool"))
cdb.register(_CurveDBEntry("brainpoolP384t1", ShortWeierstrassCurve, {
"a": 0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec50,
"b": 0x7f519eada7bda81bd826dba647910f8c4b9346ed8ccdc64e4b1abd11756dce1d2074aa263b88805ced70355a33b471ee,
"p": 0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec53,
"n": 0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202e9046565,
"h": 1,
"Gx": 0x18de98b02db9a306f2afcd7235f72a819b80ab12ebd653172476fecd462aabffc4ff191b946a5f54d8d0aa2f418808cc,
"Gy": 0x25ab056962d30651a114afd2755ad336747f93475b7a1fca3b88f2b6a208ccfe469408584dc2b2912675bf5b9e582928,
}, oid = "1.3.36.3.3.2.8.1.1.12", origin = "ECC Brainpool"))
cdb.register(_CurveDBEntry("brainpoolP512r1", ShortWeierstrassCurve, {
"a": 0x7830a3318b603b89e2327145ac234cc594cbdd8d3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c98b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94ca,
"b": 0x3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c98b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94cadc083e67984050b75ebae5dd2809bd638016f723,
"p": 0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717d4d9b009bc66842aecda12ae6a380e62881ff2f2d82c68528aa6056583a48f3,
"n": 0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca70330870553e5c414ca92619418661197fac10471db1d381085ddaddb58796829ca90069,
"h": 1,
"Gx": 0x81aee4bdd82ed9645a21322e9c4c6a9385ed9f70b5d916c1b43b62eef4d0098eff3b1f78e2d0d48d50d1687b93b97d5f7c6d5047406a5e688b352209bcb9f822,
"Gy": 0x7dde385d566332ecc0eabfa9cf7822fdf209f70024a57b1aa000c55b881f8111b2dcde494a5f485e5bca4bd88a2763aed1ca2b2fa8f0540678cd1e0f3ad80892,
}, oid = "1.3.36.3.3.2.8.1.1.13", origin = "ECC Brainpool"))
cdb.register(_CurveDBEntry("brainpoolP512t1", ShortWeierstrassCurve, {
"a": 0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717d4d9b009bc66842aecda12ae6a380e62881ff2f2d82c68528aa6056583a48f0,
"b": 0x7cbbbcf9441cfab76e1890e46884eae321f70c0bcb4981527897504bec3e36a62bcdfa2304976540f6450085f2dae145c22553b465763689180ea2571867423e,
"p": 0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717d4d9b009bc66842aecda12ae6a380e62881ff2f2d82c68528aa6056583a48f3,
"n": 0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca70330870553e5c414ca92619418661197fac10471db1d381085ddaddb58796829ca90069,
"h": 1,
"Gx": 0x640ece5c12788717b9c1ba06cbc2a6feba85842458c56dde9db1758d39c0313d82ba51735cdb3ea499aa77a7d6943a64f7a3f25fe26f06b51baa2696fa9035da,
"Gy": 0x5b534bd595f5af0fa2c892376c84ace1bb4e3019b71634c01131159cae03cee9d9932184beef216bd71df2dadf86a627306ecff96dbb8bace198b61e00f8b332,
}, oid = "1.3.36.3.3.2.8.1.1.14", origin = "ECC Brainpool"))
cdb.register(_CurveDBEntry("prime192v1", ShortWeierstrassCurve, {
"a": 0xfffffffffffffffffffffffffffffffefffffffffffffffc,
"b": 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1,
"p": 0xfffffffffffffffffffffffffffffffeffffffffffffffff,
"n": 0xffffffffffffffffffffffff99def836146bc9b1b4d22831,
"h": 1,
"Gx": 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012,
"Gy": 0x7192b95ffc8da78631011ed6b24cdd573f977a11e794811,
}, aliases = [ "secp192r1", "NIST P-192", "ansip192r1" ], oid = "1.2.840.10045.3.1.1", origin = "Certicom Standards for Efficient Cryptography (SEC) 2 / ANSI X9.62 / FIPS 186-2 / NIST Recommended Elliptic Curves for Federal Government Use"))
cdb.register(_CurveDBEntry("prime192v2", ShortWeierstrassCurve, {
"a": 0xfffffffffffffffffffffffffffffffefffffffffffffffc,
"b": 0xcc22d6dfb95c6b25e49c0d6364a4e5980c393aa21668d953,
"p": 0xfffffffffffffffffffffffffffffffeffffffffffffffff,
"n": 0xfffffffffffffffffffffffe5fb1a724dc80418648d8dd31,
"h": 1,
"Gx": 0xeea2bae7e1497842f2de7769cfe9c989c072ad696f48034a,
"Gy": 0x6574d11d69b6ec7a672bb82a083df2f2b0847de970b2de15,
}, oid = "1.2.840.10045.3.1.2", origin = "ANSI X9.62"))
cdb.register(_CurveDBEntry("prime192v3", ShortWeierstrassCurve, {
"a": 0xfffffffffffffffffffffffffffffffefffffffffffffffc,
"b": 0x22123dc2395a05caa7423daeccc94760a7d462256bd56916,
"p": 0xfffffffffffffffffffffffffffffffeffffffffffffffff,
"n": 0xffffffffffffffffffffffff7a62d031c83f4294f640ec13,
"h": 1,
"Gx": 0x7d29778100c65a1da1783716588dce2b8b4aee8e228f1896,
"Gy": 0x38a90f22637337334b49dcb66a6dc8f9978aca7648a943b0,
}, oid = "1.2.840.10045.3.1.3", origin = "ANSI X9.62"))
cdb.register(_CurveDBEntry("prime239v1", ShortWeierstrassCurve, {
"a": 0x7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc,
"b": 0x6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a,
"p": 0x7fffffffffffffffffffffff7fffffffffff8000000000007fffffffffff,
"n": 0x7fffffffffffffffffffffff7fffff9e5e9a9f5d9071fbd1522688909d0b,
"h": 1,
"Gx": 0xffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf,
"Gy": 0x7debe8e4e90a5dae6e4054ca530ba04654b36818ce226b39fccb7b02f1ae,
}, oid = "1.2.840.10045.3.1.4", origin = "ANSI X9.62"))
cdb.register(_CurveDBEntry("prime239v2", ShortWeierstrassCurve, {
"a": 0x7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc,
"b": 0x617fab6832576cbbfed50d99f0249c3fee58b94ba0038c7ae84c8c832f2c,
"p": 0x7fffffffffffffffffffffff7fffffffffff8000000000007fffffffffff,
"n": 0x7fffffffffffffffffffffff800000cfa7e8594377d414c03821bc582063,
"h": 1,
"Gx": 0x38af09d98727705120c921bb5e9e26296a3cdcf2f35757a0eafd87b830e7,
"Gy": 0x5b0125e4dbea0ec7206da0fc01d9b081329fb555de6ef460237dff8be4ba,
}, oid = "1.2.840.10045.3.1.5", origin = "ANSI X9.62"))
cdb.register(_CurveDBEntry("prime239v3", ShortWeierstrassCurve, {
"a": 0x7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc,
"b": 0x255705fa2a306654b1f4cb03d6a750a30c250102d4988717d9ba15ab6d3e,
"p": 0x7fffffffffffffffffffffff7fffffffffff8000000000007fffffffffff,
"n": 0x7fffffffffffffffffffffff7fffff975deb41b3a6057c3c432146526551,
"h": 1,
"Gx": 0x6768ae8e18bb92cfcf005c949aa2c6d94853d0e660bbf854b1c9505fe95a,
"Gy": 0x1607e6898f390c06bc1d552bad226f3b6fcfe48b6e818499af18e3ed6cf3,
}, oid = "1.2.840.10045.3.1.6", origin = "ANSI X9.62"))
cdb.register(_CurveDBEntry("prime256v1", ShortWeierstrassCurve, {
"a": 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc,
"b": 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b,
"p": 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff,
"n": 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551,
"h": 1,
"Gx": 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
"Gy": 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5,
}, aliases = [ "secp256r1", "NIST P-256" ], oid = "1.2.840.10045.3.1.7", origin = "Certicom Standards for Efficient Cryptography (SEC) 2 / ANSI X9.62 / FIPS 186-2 / NIST Recommended Elliptic Curves for Federal Government Use"))
cdb.register(_CurveDBEntry("secp112r1", ShortWeierstrassCurve, {
"a": 0xdb7c2abf62e35e668076bead2088,
"b": 0x659ef8ba043916eede8911702b22,
"p": 0xdb7c2abf62e35e668076bead208b,
"n": 0xdb7c2abf62e35e7628dfac6561c5,
"h": 1,
"Gx": 0x9487239995a5ee76b55f9c2f098,
"Gy": 0xa89ce5af8724c0a23e0e0ff77500,
}, aliases = [ "wap-wsg-idm-ecid-wtls6" ], oid = "1.3.132.0.6", alt_oids = { "wap-wsg-idm-ecid-wtls6": "2.23.43.1.4.6" }, origin = "Certicom Standards for Efficient Cryptography (SEC) 2 / Wireless Application Protocol WAP-261-WTLS-20010406a"))
cdb.register(_CurveDBEntry("secp112r2", ShortWeierstrassCurve, {
"a": 0x6127c24c05f38a0aaaf65c0ef02c,
"b": 0x51def1815db5ed74fcc34c85d709,
"p": 0xdb7c2abf62e35e668076bead208b,
"n": 0x36df0aafd8b8d7597ca10520d04b,
"h": 4,
"Gx": 0x4ba30ab5e892b4e1649dd0928643,
"Gy": 0xadcd46f5882e3747def36e956e97,
}, oid = "1.3.132.0.7", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
cdb.register(_CurveDBEntry("secp128r1", ShortWeierstrassCurve, {
"a": 0xfffffffdfffffffffffffffffffffffc,
"b": 0xe87579c11079f43dd824993c2cee5ed3,
"p": 0xfffffffdffffffffffffffffffffffff,
"n": 0xfffffffe0000000075a30d1b9038a115,
"h": 1,
"Gx": 0x161ff7528b899b2d0c28607ca52c5b86,
"Gy": 0xcf5ac8395bafeb13c02da292dded7a83,
}, oid = "1.3.132.0.28", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
cdb.register(_CurveDBEntry("secp128r2", ShortWeierstrassCurve, {
"a": 0xd6031998d1b3bbfebf59cc9bbff9aee1,
"b": 0x5eeefca380d02919dc2c6558bb6d8a5d,
"p": 0xfffffffdffffffffffffffffffffffff,
"n": 0x3fffffff7fffffffbe0024720613b5a3,
"h": 4,
"Gx": 0x7b6aa5d85e572983e6fb32a7cdebc140,
"Gy": 0x27b6916a894d3aee7106fe805fc34b44,
}, oid = "1.3.132.0.29", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
cdb.register(_CurveDBEntry("secp160k1", ShortWeierstrassCurve, {
"a": 0,
"b": 7,
"p": 0x0fffffffffffffffffffffffffffffffeffffac73,
"n": 0x100000000000000000001b8fa16dfab9aca16b6b3,
"h": 1,
"Gx": 0x03b4c382ce37aa192a4019e763036f4f5dd4d7ebb,
"Gy": 0x0938cf935318fdced6bc28286531733c3f03c4fee,
}, aliases = [ "ansip160k1" ], oid = "1.3.132.0.9", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
cdb.register(_CurveDBEntry("secp160r1", ShortWeierstrassCurve, {
"a": 0x0ffffffffffffffffffffffffffffffff7ffffffc,
"b": 0x01c97befc54bd7a8b65acf89f81d4d4adc565fa45,
"p": 0x0ffffffffffffffffffffffffffffffff7fffffff,
"n": 0x100000000000000000001f4c8f927aed3ca752257,
"h": 1,
"Gx": 0x04a96b5688ef573284664698968c38bb913cbfc82,
"Gy": 0x023a628553168947d59dcc912042351377ac5fb32,
}, aliases = [ "ansip160r1" ], oid = "1.3.132.0.8", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
cdb.register(_CurveDBEntry("secp160r2", ShortWeierstrassCurve, {
"a": 0x0fffffffffffffffffffffffffffffffeffffac70,
"b": 0x0b4e134d3fb59eb8bab57274904664d5af50388ba,
"p": 0x0fffffffffffffffffffffffffffffffeffffac73,
"n": 0x100000000000000000000351ee786a818f3a1a16b,
"h": 1,
"Gx": 0x052dcb034293a117e1f4ff11b30f7199d3144ce6d,
"Gy": 0x0feaffef2e331f296e071fa0df9982cfea7d43f2e,
}, aliases = [ "ansip160r2", "wap-wsg-idm-ecid-wtls7" ], oid = "1.3.132.0.30", alt_oids = { "wap-wsg-idm-ecid-wtls7": "2.23.43.1.4.7" }, origin = "Certicom Standards for Efficient Cryptography (SEC) 2 / Wireless Application Protocol WAP-261-WTLS-20010406a"))
cdb.register(_CurveDBEntry("secp192k1", ShortWeierstrassCurve, {
"a": 0,
"b": 3,
"p": 0xfffffffffffffffffffffffffffffffffffffffeffffee37,
"n": 0xfffffffffffffffffffffffe26f2fc170f69466a74defd8d,
"h": 1,
"Gx": 0xdb4ff10ec057e9ae26b07d0280b7f4341da5d1b1eae06c7d,
"Gy": 0x9b2f2f6d9c5628a7844163d015be86344082aa88d95e2f9d,
}, aliases = [ "ansip192k1" ], oid = "1.3.132.0.31", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
cdb.register(_CurveDBEntry("secp224k1", ShortWeierstrassCurve, {
"a": 0,
"b": 5,
"p": 0x0fffffffffffffffffffffffffffffffffffffffffffffffeffffe56d,
"n": 0x10000000000000000000000000001dce8d2ec6184caf0a971769fb1f7,
"h": 1,
"Gx": 0x0a1455b334df099df30fc28a169a467e9e47075a90f7e650eb6b7a45c,
"Gy": 0x07e089fed7fba344282cafbd6f7e319f7c0b0bd59e2ca4bdb556d61a5,
}, aliases = [ "ansip224k1" ], oid = "1.3.132.0.32", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
cdb.register(_CurveDBEntry("secp224r1", ShortWeierstrassCurve, {
"a": 0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe,
"b": 0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4,
"p": 0xffffffffffffffffffffffffffffffff000000000000000000000001,
"n": 0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d,
"h": 1,
"Gx": 0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21,
"Gy": 0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34,
}, aliases = [ "ansip224r1", "NIST P-224", "wap-wsg-idm-ecid-wtls12" ], oid = "1.3.132.0.33", alt_oids = { "wap-wsg-idm-ecid-wtls12": "2.23.43.1.4.12" }, origin = "Certicom Standards for Efficient Cryptography (SEC) 2 / FIPS 186-2 / NIST Recommended Elliptic Curves for Federal Government Use / Wireless Application Protocol WAP-261-WTLS-20010406a"))
cdb.register(_CurveDBEntry("secp256k1", ShortWeierstrassCurve, {
"a": 0,
"b": 7,
"p": 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,
"n": 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141,
"h": 1,
"Gx": 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
"Gy": 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8,
}, aliases = [ "ansip256k1" ], oid = "1.3.132.0.10", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
cdb.register(_CurveDBEntry("secp384r1", ShortWeierstrassCurve, {
"a": 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc,
"b": 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef,
"p": 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff,
"n": 0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973,
"h": 1,
"Gx": 0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7,
"Gy": 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f,
}, aliases = [ "ansip384r1", "NIST P-384" ], oid = "1.3.132.0.34", origin = "Certicom Standards for Efficient Cryptography (SEC) 2 / FIPS 186-2 / NIST Recommended Elliptic Curves for Federal Government Use"))
cdb.register(_CurveDBEntry("secp521r1", ShortWeierstrassCurve, {
"a": 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc,
"b": 0x051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00,
"p": 0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
"n": 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409,
"h": 1,
"Gx": 0x0c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66,
"Gy": 0x11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650,
}, aliases = [ "NIST P-521", "ansip521r1" ], oid = "1.3.132.0.35", origin = "Certicom Standards for Efficient Cryptography (SEC) 2 / FIPS 186-2 / NIST Recommended Elliptic Curves for Federal Government Use"))
cdb.register(_CurveDBEntry("wap-wsg-idm-ecid-wtls8", ShortWeierstrassCurve, {
"a": 0,
"b": 3,
"p": 0x0fffffffffffffffffffffffffde7,
"n": 0x100000000000001ecea551ad837e9,
"h": 1,
"Gx": 1,
"Gy": 2,
}, oid = "2.23.43.1.4.8", origin = "Wireless Application Protocol WAP-261-WTLS-20010406a"))
cdb.register(_CurveDBEntry("wap-wsg-idm-ecid-wtls9", ShortWeierstrassCurve, {
"a": 0,
"b": 3,
"p": 0x0fffffffffffffffffffffffffffffffffffc808f,
"n": 0x100000000000000000001cdc98ae0e2de574abf33,
"h": 1,
"Gx": 1,
"Gy": 2,
}, oid = "2.23.43.1.4.9", origin = "Wireless Application Protocol WAP-261-WTLS-20010406a"))
cdb.register(_CurveDBEntry("Curve25519", MontgomeryCurve, {
"a": 486662,
"b": 1,
"p": (2 ** 255) - 19,
"n": (2 ** 252) + 27742317777372353535851937790883648493,
"h": 8,
"Gx": 0x9,
"Gy": 0x5f51e65e475f794b1fe122d388b72eb36dc2b28192839e4dd6163a5d81312c14,
}, origin = "2006 Bernstein"))
# Curve imported from IETF https://tools.ietf.org/html/rfc7748
cdb.register(_CurveDBEntry("Curve448", MontgomeryCurve, {
"a": 156326,
"b": 1,
"p": (2 ** 448) - (2 ** 224) - 1,
"n": (2 ** 446) - 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d,
"h": 4,
"Gx": 0x5,
"Gy": 0x7d235d1295f5b1f66c98ab6e58326fcecbae5d34f55545d060f75dc28df3f6edb8027e2346430d211312c4b150677af76fd7223d457b5b1a,
}, origin = "2006 Bernstein"))
cdb.register(_CurveDBEntry("Ed25519", TwistedEdwardsCurve, {
"a": -1,
"d": 37095705934669439343138083508754565189542113879843219016388785533085940283555,
"p": (2 ** 255) - 19,
"n": (2 ** 252) + 27742317777372353535851937790883648493,
"h": 8,
"Gx": 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a,
"Gy": 0x6666666666666666666666666666666666666666666666666666666666666658,
}, origin = "2011 Bernstein-Duif-Lange-Schwabe-Yang", quirks = [ CurveQuirkEdDSASetPrivateKeyMSB(), CurveQuirkEdDSAEnsurePrimeOrderSubgroup(), CurveQuirkSigningHashFunction("sha512") ]))
# Curve imported from SafeCurves http://safecurves.cr.yp.to
cdb.register(_CurveDBEntry("Anomalous", ShortWeierstrassCurve, {
"a": 0x98d0fac687d6343eb1a1f595283eb1a1f58d0fac687d635f5e4,
"b": 0x4a1f58d0fac687d6343eb1a5e2d6343eb1a1f58d0fac688ab3f,
"p": 0xb0000000000000000000000953000000000000000000001f9d7,
"n": 0xb0000000000000000000000953000000000000000000001f9d7,
"h": 1,
"Gx": 0x101efb35fd1963c4871a2d17edaafa7e249807f58f8705126c6,
"Gy": 0x22389a3954375834304ba1d509a97de6c07148ea7f5951b20e7,
}, secure = False, origin = "Bernstein http://safecurves.cr.yp.to illustration of additive transfer and small discriminant"))
# Curve imported from SafeCurves http://safecurves.cr.yp.to
cdb.register(_CurveDBEntry("M-221", MontgomeryCurve, {
"a": 117050,
"b": 1,
"p": 0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffd,
"n": 0x40000000000000000000000000015a08ed730e8a2f77f005042605b,
"h": 8,
"Gx": 4,
"Gy": 0xf7acdd2a4939571d1cef14eca37c228e61dbff10707dc6c08c5056d,
}, aliases = [ "Curve2213" ], origin = "2013 Aranha-Barreto-Pereira-Ricardini"))
# Curve imported from SafeCurves http://safecurves.cr.yp.to
cdb.register(_CurveDBEntry("E-222", TwistedEdwardsCurve, {
"a": 1,
"d": 160102,
"p": 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffff8b,
"n": 0xffffffffffffffffffffffffffff70cbc95e932f802f31423598cbf,
"h": 4,
"Gx": 0x19b12bb156a389e55c9768c303316d07c23adab3736eb2bc3eb54e51,
"Gy": 28,
}, origin = "2013 Aranha-Barreto-Pereira-Ricardini"))
# Curve imported from SafeCurves http://safecurves.cr.yp.to
cdb.register(_CurveDBEntry("Curve1174", TwistedEdwardsCurve, {
"a": 1,
"d": -1174,
"p": 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7,
"n": 0x1fffffffffffffffffffffffffffffff77965c4dfd307348944d45fd166c971,
"h": 4,
"Gx": 0x37fbb0cea308c479343aee7c029a190c021d96a492ecd6516123f27bce29eda,
"Gy": 0x6b72f82d47fb7cc6656841169840e0c4fe2dee2af3f976ba4ccb1bf9b46360e,
}, origin = "2013 Bernstein-Hamburg-Krasnova-Lange"))
# Curve imported from SafeCurves http://safecurves.cr.yp.to
cdb.register(_CurveDBEntry("BN(2,254)", ShortWeierstrassCurve, {
"a": 0,
"b": 2,
"p": 0x2523648240000001ba344d80000000086121000000000013a700000000000013,
"n": 0x2523648240000001ba344d8000000007ff9f800000000010a10000000000000d,
"h": 1,
"Gx": -1,
"Gy": 1,
}, origin = "2011 Pereira-Simplicio-Naehrig-Barreto"))
# Curve imported from SafeCurves http://safecurves.cr.yp.to
cdb.register(_CurveDBEntry("ANSSI FRP256v1", ShortWeierstrassCurve, {
"a": -3,
"b": 0xee353fca5428a9300d4aba754a44c00fdfec0c9ae4b1a1803075ed967b7bb73f,
"p": 0xf1fd178c0b3ad58f10126de8ce42435b3961adbcabc8ca6de8fcf353d86e9c03,
"n": 0xf1fd178c0b3ad58f10126de8ce42435b53dc67e140d2bf941ffdd459c6d655e1,
"h": 1,
"Gx": 0xb6b3d4c356c139eb31183d4749d423958c27d2dcaf98b70164c97a2dd98f5cff,
"Gy": 0x6142e0f7c8b204911f9271f0f3ecef8c2701c307e8e4c9e183115a1554062cfb,
}, oid = "1.2.250.1.223.101.256.1", origin = "Agence nationale de la sécurité des systèmes d'information"))
# Curve imported from SafeCurves http://safecurves.cr.yp.to
cdb.register(_CurveDBEntry("E-382", TwistedEdwardsCurve, {
"a": 1,
"d": -67254,
"p": 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff97,
"n": 0xfffffffffffffffffffffffffffffffffffffffffffffffd5fb21f21e95eee17c5e69281b102d2773e27e13fd3c9719,
"h": 4,
"Gx": 0x196f8dd0eab20391e5f05be96e8d20ae68f840032b0b64352923bab85364841193517dbce8105398ebc0cc9470f79603,
"Gy": 17,
}, origin = "2013 Aranha-Barreto-Pereira-Ricardini"))
# Curve imported from SafeCurves http://safecurves.cr.yp.to
cdb.register(_CurveDBEntry("M-383", MontgomeryCurve, {
"a": 2065150,
"b": 1,
"p": 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff45,
"n": 0x10000000000000000000000000000000000000000000000006c79673ac36ba6e7a32576f7b1b249e46bbc225be9071d7,
"h": 8,
"Gx": 12,
"Gy": 0x1ec7ed04aaf834af310e304b2da0f328e7c165f0e8988abd3992861290f617aa1f1b2e7d0b6e332e969991b62555e77e,
}, origin = "2013 Aranha-Barreto-Pereira-Ricardini"))
# Curve imported from SafeCurves http://safecurves.cr.yp.to
cdb.register(_CurveDBEntry("Curve383187", MontgomeryCurve, {
"a": 229969,
"b": 1,
"p": 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff45,
"n": 0x1000000000000000000000000000000000000000000000000e85a85287a1488acd41ae84b2b7030446f72088b00a0e21,
"h": 8,
"Gx": 5,
"Gy": 0x1eebe07dc1871896732b12d5504a32370471965c7a11f2c89865f855ab3cbd7c224e3620c31af3370788457dd5ce46df,
}, origin = "2013 Aranha-Barreto-Pereira-Ricardini"))
# Curve imported from SafeCurves http://safecurves.cr.yp.to
cdb.register(_CurveDBEntry("Curve41417", TwistedEdwardsCurve, {
"a": 1,
"d": 3617,
"p": 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffef,
"n": 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffeb3cc92414cf706022b36f1c0338ad63cf181b0e71a5e106af79,
"h": 8,
"Gx": 0x1a334905141443300218c0631c326e5fcd46369f44c03ec7f57ff35498a4ab4d6d6ba111301a73faa8537c64c4fd3812f3cbc595,
"Gy": 34,
}, aliases = [ "Curve3617" ], origin = "2013 Bernstein-Lange"))
# Curve imported from SafeCurves http://safecurves.cr.yp.to
cdb.register(_CurveDBEntry("Ed448-Goldilocks", TwistedEdwardsCurve, {
"a": 1,
"d": -39081,
"p": 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
"n": 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3,
"h": 4,
"Gx": 0x297ea0ea2692ff1b4faff46098453a6a26adf733245f065c3c59d0709cecfa96147eaaf3932d94c63d96c170033f4ba0c7f0de840aed939f,
"Gy": 19,
}, origin = "2014 Hamburg", quirks = [ CurveQuirkEdDSASetPrivateKeyMSB(), CurveQuirkEdDSAEnsurePrimeOrderSubgroup(), CurveQuirkSigningHashFunction("shake256-114") ]))
# Curve imported from https://tools.ietf.org/html/rfc8032
cdb.register(_CurveDBEntry("Ed448", TwistedEdwardsCurve, {
"a": 1,
"d": -39081,
"p": 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
"n": 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3,
"h": 4,
"Gx": 0x4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e,
"Gy": 0x693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14,
}, origin = "https://tools.ietf.org/html/rfc8032", quirks = [ CurveQuirkEdDSASetPrivateKeyMSB(), CurveQuirkEdDSAEnsurePrimeOrderSubgroup(), CurveQuirkSigningHashFunction("shake256-114") ]))
# Curve imported from SafeCurves http://safecurves.cr.yp.to
cdb.register(_CurveDBEntry("M-511", MontgomeryCurve, {
"a": 530438,
"b": 1,
"p": 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff45,
"n": 0x100000000000000000000000000000000000000000000000000000000000000017b5feff30c7f5677ab2aeebd13779a2ac125042a6aa10bfa54c15bab76baf1b,
"h": 8,
"Gx": 5,
"Gy": 0x2fbdc0ad8530803d28fdbad354bb488d32399ac1cf8f6e01ee3f96389b90c809422b9429e8a43dbf49308ac4455940abe9f1dbca542093a895e30a64af056fa5,
}, aliases = [ "Curve511187" ], origin = "2013 Aranha-Barreto-Pereira-Ricardini"))
# Curve imported from SafeCurves http://safecurves.cr.yp.to
cdb.register(_CurveDBEntry("E-521", TwistedEdwardsCurve, {
"a": 1,
"d": -376014,
"p": 0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
"n": 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd15b6c64746fc85f736b8af5e7ec53f04fbd8c4569a8f1f4540ea2435f5180d6b,
"h": 4,
"Gx": 0x752cb45c48648b189df90cb2296b2878a3bfd9f42fc6c818ec8bf3c9c0c6203913f6ecc5ccc72434b1ae949d568fc99c6059d0fb13364838aa302a940a2f19ba6c,
"Gy": 12,
}, origin = "2013 Bernstein-Lange / 2013 Hamburg / 2013 Aranha-Barreto-Pereira-Ricardini"))
cdb.register(_CurveDBEntry("rigol", ShortWeierstrassCurve, {
"a": 0x2982,
"b": 0x3408,
"p": 0xaebf94cee3e707,
"n": 0xaebf94d5c6aa71,
"h": 1,
"Gx": 0x7a3e808599a525,
"Gy": 0x28be7fafd2a052,
}, origin = "Rigol DS2xxx feature activation curve"))
def getcurvedb():
"""Returns an instance of the curve database singleton object."""
return CurveDB()
def getcurvenames():
"""Returns the names of all curves known to toyecc."""
return CurveDB().curvenames()
def getcurveentry(name):
"""Returns a curve entry by its name."""
return CurveDB().getentry(name)
def getcurvebyname(name):
"""Returns a curve by its name."""
return CurveDB()[name]

140
toyecc/CurveOps.py Normal file
View File

@ -0,0 +1,140 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
from .FieldElement import FieldElement
from .Exceptions import NoSuchCurveException
class CurveOpIsomorphism(object):
def _twist(self, d = None, sqrt_d = None):
"""Returns the twisted curve with the twist coefficient d. If d is a
quadratic non-residue mod p then this function will yield a curve that
is isomorphous on the field extension GF(sqrt(d)). If it is a quadratic
residue, it will return an GF(p)-isomorphous curve."""
assert(self.curvetype == "shortweierstrass")
ShortWeierstrassCurve = self.__class__
if sqrt_d is not None:
# If a square root is given, then it must be a correct square root
assert(sqrt_d ** 2 == d)
a = self.a * (d ** 2)
b = self.b * (d ** 3)
if d.is_qr and self.hasgenerator:
# Quadratic twist will return an GF(p)-isomorphous curve -> convert
# generator point as well
if sqrt_d is None:
sqrt_d = d.sqrt()[0]
Gx = int(self.G.x * d)
Gy = int(self.G.y * (sqrt_d ** 3))
n = self.n
h = self.h
else:
# Quadratic twist will return an isomorphous curve on the
# GF(sqrt(d)) field extension -> no generator point conversion for
# now
# Gx = int(self.G.x * d)
# Gy = int(self.G.y * d)
Gx = None
Gy = None
# If the original curve had q + 1 - t points, then its twist will
# have q + 1 + t points. TODO: Does this help us to find the order
# of the generator point G? I don't think it does :-( Leave n and h
# therefore unset for the moment.
n = None
h = None
return ShortWeierstrassCurve(a = int(a), b = int(b), p = self.p, n = n, h = h, Gx = Gx, Gy = Gy)
def twist(self, d = None):
"""If the twist coefficient d is omitted, the function will
automatically look for an arbitrary quadratic non-residue in F_P."""
if d == 0:
raise Exception("Domain error: d must be nonzero.")
elif d is None:
# Search for a QNR in F_P
d = FieldElement.any_qnr(self.p)
else:
d = FieldElement(d, self.p)
if d.is_qr:
raise Exception("Twist requested, but twist coefficient d is a quadratic-residue mod p. Refusing to return a GF(p)-isomorphic curve; if you want this behavior, use twist_fp_isomorphic()")
return self._twist(d)
def twist_fp_isomorphic(self, u):
"""Returns a GF(p)-isomorphous curve by applying the substituting
transformation x = u^2 x' and y = u^3 y' on the curve equation. The
function therefore returns a quadratic twist with d = u^2, i.e. it
ensures that the twist coefficient d is a quadratic residue mod p.."""
if u == 0:
raise Exception("Domain error: u must be nonzero.")
return self._twist(FieldElement(u ** 2, self.p), FieldElement(u, self.p))
def twist_fp_isomorphic_fixed_a(self, a):
"""Tries to find an GF(p)-isomorphous curve which has a particular
given value for the curve coefficient 'a'."""
# anew = a * u^4 -> u = quartic_root(anew / a)
scalar = a // self.a
u = scalar.quartic_root()
if u is None:
raise NoSuchCurveException("Cannot find an isomorphism so that a = %d because %s has no quartic root in F_P" % (a, scalar))
return self.twist_fp_isomorphic(int(u))
def is_isomorphous_curve(self, other):
"""Returns if the given curve 'other' is isomorphous in the same field
as the given curve curve."""
if other.p != self.p:
return False
try:
iso = self.twist_fp_isomorphic_fixed_a(other.a)
except NoSuchCurveException:
# No isomorphous curve with this value for a exists
return False
# The curves should be identical after the transformation if they're
# isomorphous to each other
return (iso.a == other.a) and (iso.b == other.b)
class CurveOpExportSage(object):
def export_sage(self, varname = "curve"):
"""Exports the elliptic curve to statements that can be used within the
SAGE computer algebra system."""
# EllipticCurve([a1,a2,a3,a4,a6]) means in Sage:
# y² + a1 x y + a3 y = x³ + a2 x² + a4 x + a6
# i.e. for Short Weierstrass a4 = A, a6 = B
statements = [ ]
statements.append("# %s" % (str(self)))
statements.append("%s_p = 0x%x" % (varname, int(self.p)))
statements.append("%s_F = GF(%s_p)" % (varname, varname))
if self.curvetype == "shortweierstrass":
statements.append("%s_a = 0x%x" % (varname, int(self.a)))
statements.append("%s_b = 0x%x" % (varname, int(self.b)))
statements.append("%s = EllipticCurve(%s_F, [ %s_a, %s_b ])" % (varname, varname, varname, varname))
else:
raise Exception(NotImplemented)
return statements

77
toyecc/CurveQuirks.py Normal file
View File

@ -0,0 +1,77 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
import hashlib
class CurveQuirk(object):
identifier = None
@property
def identity(self):
return (self.identifier, )
def __eq__(self, other):
return self.identity == other.identity
def __ne__(self, other):
return not (self == other)
def __lt__(self, other):
return self.identity < other.identity
def __hash__(self):
return hash(self.identity)
def __str__(self):
return self.identifier
class CurveQuirkEdDSASetPrivateKeyMSB(CurveQuirk):
"""Set the highest significant bit of the private key during EdDSA
signature generation. For example, for EdDSA signatures on Ed25519, this
would bitwise or the value 'a' with 2^254."""
identifier = "EdDSA_set_private_key_MSB"
class CurveQuirkEdDSAEnsurePrimeOrderSubgroup(CurveQuirk):
"""Ensures during EdDSA signature generation that the private key is on a
prime-order subgroup. This is done by clearing the amount of bits that is
required by the cofactor of the curve (which has to be a power of two for
this quirk to work, otherwise it'll fail at runtime). Concretely, for EdDSA
on Ed25519 this means that the least significant three bits would be set to
zero because the curve cofactor is 8."""
identifier = "EdDSA_use_prime_order_subgroup"
class CurveQuirkSigningHashFunction(CurveQuirk):
"""For some curves, the signing hash function is implicitly given. In
particular for the Ed448 and Ed25519 variants, this is true. Encode these
as a curve quirk."""
identifier = "signing_hash_function"
def __init__(self, sig_fnc_name):
self._sig_fnc_name = sig_fnc_name
def hashdata(self, data):
hash_fnc = {
"sha512": lambda x: hashlib.sha512(data).digest(),
"shake256-114": lambda x: hashlib.shake_256(data).digest(114),
}
return hash_fnc[self._sig_fnc_name](data)

View File

@ -0,0 +1,67 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
from .Polynomial import Polynomial
class DivisionPolynomial(object):
def __init__(self, curve):
"""Creates a division polynomial generator which returns \psi_i for the
given curve in Weierstrass form."""
self._curve = curve
assert(self._curve.curvetype == "shortweierstrass")
self._cache = { }
self._curvepoly = None
self._initcache()
def _initcache(self):
(a, b) = (self.curve.a, self.curve.b)
x = Polynomial(self.curve.p)
self._cache[0] = Polynomial(self.curve.p, 0)
self._cache[1] = Polynomial(self.curve.p, 1)
self._cache[2] = Polynomial(self.curve.p, 2)
self._cache[3] = (3 * x**4) + (6 * a * x**2) + (12 * b * x) - (a**2)
self._cache[4] = 4 * (x**6 + (5 * a * x**4) + (20 * b * x**3) - (5 * a**2 * x**2) - (4 * a * b * x) - (8 * b**2) - (a**3))
self._curvepoly = x**3 + (a * x) + b
@property
def curve(self):
return self._curve
def __getitem__(self, index):
if index not in self._cache:
m = index // 2
if (index % 2) == 1:
# The paper says this would be correct:
# result = (self[m + 2] * self[m]**3) - (self[m - 1] * self[m + 1] ** 3)
# But MIRACL does it differently. Use the MIRACL approach:
if (m % 2) == 0:
result = (self._curvepoly**2 * self[m + 2] * self[m]**3) - (self[m - 1] * self[m + 1]**3)
else:
result = (self[m + 2] * self[m]**3) - (self._curvepoly**2 * self[m - 1] * self[m + 1]**3)
else:
result = (self[m] // 2) * ((self[m + 2] * self[m - 1]**2) - (self[m - 2] * self[m + 1]**2))
self._cache[index] = result
return self._cache[index]
def __str__(self):
return "DivisionPolys<%s, %d cached>" % (str(self.curve), len(self._cache))

41
toyecc/DocInherit.py Normal file
View File

@ -0,0 +1,41 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2016 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
def doc_inherit(superclass):
"""Inherit the docstring of a parent method. The super class needs to be
given as the decorator's parameter. Does not overwrite the docstring if
there is already one present."""
def decorator(decoree):
method_name = decoree.__name__
if decoree.__doc__ is None:
parent_method = getattr(superclass, method_name, None)
if parent_method is None:
raise Exception("Tried to inherit docstring of method '%s' from class '%s', but the latter does not offer a method by that name." % (method_name, str(superclass)))
docstr = parent_method.__doc__
if docstr is None:
raise Exception("Tried to inherit docstring of method '%s' from class '%s', but that method also does not have a docstring." % (method_name, str(superclass)))
decoree.__doc__ = docstr
else:
raise Exception("Tried to overwrite an already present docstring of %s" % (str(decoree)))
return decoree
return decorator

79
toyecc/ECPrivateKey.py Normal file
View File

@ -0,0 +1,79 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
from .PrivKeyOps import PrivKeyOpECDSASign, PrivKeyOpECIESDecrypt, PrivKeyOpEDDSASign, PrivKeyOpEDDSAKeyGen, PrivKeyOpEDDSAEncode, PrivKeyOpECDH, PrivKeyOpLoad
from .ECPublicKey import ECPublicKey
from .Random import secure_rand_int_between
class ECPrivateKey(PrivKeyOpECDSASign, PrivKeyOpECIESDecrypt, PrivKeyOpEDDSASign, PrivKeyOpEDDSAKeyGen, PrivKeyOpEDDSAEncode, PrivKeyOpECDH, PrivKeyOpLoad):
"""Represents an elliptic curve private key."""
def __init__(self, scalar, curve):
"""Initialize the private key with the given scalar on the given
curve."""
self._seed = None
self._scalar = scalar
self._curve = curve
self._pubkey = ECPublicKey(self._scalar * self._curve.G)
@property
def scalar(self):
"""Returns the private scalar d of the key."""
return self._scalar
@property
def curve(self):
"""Returns the group which is used for EC computations."""
return self._curve
@property
def pubkey(self):
"""Returns the public key that is the counterpart to this private key."""
return self._pubkey
@property
def seed(self):
"""Returns the seed or None if there wasn't one. A seed is used for
schemes like EdDSA; it basically is a binary string that is hashed to
yield that actual private scalar d."""
return self._seed
def set_seed(self, seed):
"""Sets the seed of the private key. This operation can only performed
if no scalar has previously been set for this key."""
assert(self._seed is None)
self._seed = seed
return self
@staticmethod
def generate(curve):
"""Generate a random private key on a given curve."""
scalar = secure_rand_int_between(1, curve.n - 1)
return ECPrivateKey(scalar, curve)
def __str__(self):
if self._seed is None:
return "PrivateKey<d = 0x%x>" % (self.scalar)
else:
seedstr = "".join("%02x" % (c) for c in self._seed)
return "PrivateKey<d = 0x%x, seed = %s>" % (self.scalar, seedstr)

44
toyecc/ECPublicKey.py Normal file
View File

@ -0,0 +1,44 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2016 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
from .PubKeyOps import PubKeyOpECDSAVerify, PubKeyOpECDSAExploitReusedNonce, PubKeyOpEDDSAVerify, PubKeyOpEDDSAEncode, PubKeyOpECIESEncrypt, PubKeyOpLoad
class ECPublicKey(PubKeyOpECDSAVerify, PubKeyOpECDSAExploitReusedNonce, PubKeyOpEDDSAVerify, PubKeyOpEDDSAEncode, PubKeyOpECIESEncrypt, PubKeyOpLoad):
"""Elliptic curve public key abstraction. An EC public key is just a point
on the curve, which is why the constructor only takes this (public) point
as a parameter. The public key abstraction allows this point to be used in
various meaningful purposes (ECDSA signature verification, etc.)."""
def __init__(self, point):
self._point = point
@property
def curve(self):
return self._point.curve
@property
def point(self):
return self._point
def __str__(self):
return "PublicKey<%s>" % (str(self.point))

202
toyecc/EllipticCurve.py Normal file
View File

@ -0,0 +1,202 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
from .AffineCurvePoint import AffineCurvePoint
class EllipticCurve(object):
"""Elliptic curve base class. Provides functionality which all curves have
in common."""
def __init__(self, p, n, h, Gx, Gy, **kwargs):
assert(isinstance(p, int)) # Modulus
assert((n is None) or isinstance(n, int)) # Order
assert((h is None) or isinstance(h, int)) # Cofactor
assert((Gx is None) or isinstance(Gx, int)) # Generator Point X
assert((Gy is None) or isinstance(Gy, int)) # Generator Point Y
assert((Gx is None) == (Gy is None)) # Either both X and Y of G are set or none
self._p = p
self._n = n
self._h = h
if (Gx is not None) and (Gy is not None):
self._G = AffineCurvePoint(Gx, Gy, self)
else:
self._G = None
if "quirks" in kwargs:
self._quirks = { quirk.identifier: quirk for quirk in kwargs["quirks"] }
else:
self._quirks = { }
@property
def p(self):
"""Returns the prime modulus which constitutes the finite field in
which the curve lies."""
return self._p
@property
def n(self):
"""Returns the order of the subgroup that is created by the generator
G."""
return self._n
@property
def h(self):
"""Returns the cofactor of the generator subgroup, i.e. h = #E(F_p) /
n. This will always be an integer according to Lagrange's Theorem."""
return self._h
@property
def G(self):
"""Returns the generator point G of the curve or None if no such point
was set. The generator point generates a subgroup over #E(F_p)."""
return self._G
@property
def curve_order(self):
"""Returns the order of the curve in the underlying field, i.e.
#E(F_p). Intuitively, this is the total number of points on the curve
(plus maybe points at ininity, depending on the curve type) that
satisfy the curve equation."""
if (self.h is None) or (self.n is None):
raise Exception("#E(F_p) is unknown for this curve")
return self.h * self.n
@property
def frobenius_trace(self):
"""Returns the Frobenius trace 't' of the curve. Since
#E(F_p) = p + 1 - t it follows that t = p + 1 - #E(F_p)."""
return self.p + 1 - self.curve_order
@property
def domainparams(self):
"""Returns the curve parameters as a named tuple."""
raise Exception(NotImplemented)
@property
def hasgenerator(self):
"""Returns if a generator point was supplied for the curve."""
return self.G is not None
@property
def hasname(self):
"""Returns if the curve is named (i.e. its name is not None)."""
return self.name is not None
@property
def name(self):
"""Returns the name of the curve, if it was given one during
construction. Purely informational."""
return self._name
@property
def prettyname(self):
"""Returns the pretty name of the curve type. This might depend on the
actual curve, since it may also vary on the actual domain parameters to
include if the curve is a Koblitz curve or not."""
return self.pretty_name
@property
def curvetype(self):
"""Returns a string that corresponds to the curve type. For example,
this string can be 'shortweierstrass', 'twistededwards' or
'montgomery'."""
raise Exception(NotImplemented)
@property
def domainparamdict(self):
"""Returns the domain parameters of the curve as a dictionary."""
return dict(self.domainparams._asdict())
@property
def security_bit_estimate(self):
"""Gives a haphazard estimate of the security of the underlying field,
in bits. For most curves, this will be half the bitsize of n (but might
be less, for example for Koblitz curves some bits might be
subtracted)."""
return self.n.bit_length() // 2
def enumerate_points(self):
"""Enumerates all points on the curve, including the point at infinity
(if the curve has such a special point)."""
raise Exception(NotImplemented)
def naive_order_calculation(self):
"""Naively calculates the order #E(F_p) of the curve by enumerating and
counting all points which fulfull the curve equation. Note that this
implementation only works for the smallest of curves and is
computationally infeasible for all practical applications."""
order = 0
for pt in self.enumerate_points():
order += 1
return order
def neutral(self):
"""Returns the neutral element of the curve group (for some curves,
this will be the point at infinity)."""
return AffineCurvePoint(None, None, self)
def is_neutral(self, P):
"""Checks if a given point P is the neutral element of the group."""
return P.x is None
def oncurve(self, P):
"""Checks is a given point P is on the curve."""
raise Exception(NotImplemented)
def point_addition(self, P, Q):
"""Returns the sum of two points P and Q on the curve."""
raise Exception(NotImplemented)
def point_conjugate(self, P):
"""Returns the negated point -P to a given point P."""
raise Exception(NotImplemented)
def compress(self, P):
"""Returns the compressed representation of the point P on the
curve. Not all curves may support this operation."""
raise Exception(NotImplemented)
def uncompress(self, compressed):
"""Returns the uncompressed representation of a point on the curve. Not
all curves may support this operation."""
raise Exception(NotImplemented)
def has_quirk(self, quirk_class):
"""Some elliptic curves may have quirks or tweaks for certain
algorithms. These are attached to the curve using the 'quirks' kwarg of
the constructor. Code that wants to query if a specific quirk is
present may do so by calling 'has_quirk' with the according quirk class
(not a quirk class instance!)."""
return quirk_class.identifier in self._quirks
def get_quirk(self, quirk_class):
"""If a quirk is present for a given elliptic curve, this quirk may
have been parametrized during instanciation. The get_quirk() method
returns that quirk instance when given a specific quirk class as input.
It raises a KeyError if the requested quirk is not present for the
elliptic curve."""
return self._quirks[quirk_class.identifier]
def __eq__(self, other):
return self.domainparams == other.domainparams
def __ne__(self, other):
return not (self == other)

34
toyecc/Exceptions.py Normal file
View File

@ -0,0 +1,34 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
class DuplicateCurveException(Exception):
pass
class NoSuchCurveException(Exception):
pass
class UnsupportedPointFormatException(Exception):
pass
class UnsupportedFieldException(Exception):
pass

249
toyecc/FieldElement.py Normal file
View File

@ -0,0 +1,249 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
import random
class FieldElement(object):
"""Represents an element in a finite field over a (prime) modulus."""
def __init__(self, intvalue, modulus):
assert(isinstance(intvalue, int))
assert(isinstance(modulus, int))
self._intvalue = intvalue % modulus
self._modulus = modulus
self._qnr = None
@property
def modulus(self):
"""Returns the field's modulus."""
return self._modulus
@staticmethod
def _eea(a, b):
"""Extended euclidian algorithm. Returns the gcd of (a, b) and the
Bezout-coefficients."""
assert(isinstance(a, int))
assert(isinstance(b, int))
(s, t, u, v) = (1, 0, 0, 1)
while b != 0:
(q, r) = (a // b, a % b)
(unew, vnew) = (s, t)
s = u - (q * s)
t = v - (q * t)
(a, b) = (b, r)
(u, v) = (unew, vnew)
return (a, u, v)
def inverse(self):
if int(self) == 0:
raise Exception("Trying to invert zero")
(gcd, u, v) = self._eea(int(self), self.modulus)
return FieldElement(v, self.modulus)
@property
def is_qr(self):
"""Returns if the number is a quadratic residue according to Euler's
criterion."""
return not self.is_qnr
@property
def is_qnr(self):
"""Returns if the number is a quadratic non-residue according to
Euler's criterion."""
if self._qnr is None:
self._qnr = int(self ** ((self._modulus - 1) // 2)) != 1
return self._qnr
@property
def legrende_symbol(self):
"""Returns the Legrende symbol of the field element, i.e. 0 if the
element is 0 mod p, 1 if it is a quadratic residue mod p or -1 if it is
a quadratic non-residue mod p."""
if self == 0:
return 0
elif self.is_qr:
return 1
else:
return -1
def _tonelli_shanks_sqrt(self):
"""Performs the Tonelli-Shanks algorithm to determine the square root
on an element. Note that the algorithm only works if the value it is
performed on is a quadratic residue mod p."""
q = self._modulus - 1
s = 0
while (q % 2) == 0:
s += 1
q >>= 1
assert(q * (2 ** s) == self.modulus - 1)
while True:
z = FieldElement(random.randint(1, self.modulus - 1), self.modulus)
if z.is_qnr:
break
assert(z.is_qnr)
c = z ** q
r = self ** ((q + 1) // 2)
t = self ** q
m = s
while int(t) != 1:
for i in range(1, m):
if int(t ** (1 << i)) == 1:
break
b = c ** (1 << (m - i - 1))
r = r * b
t = t * (b ** 2)
c = b ** 2
m = i
return r
def sqr(self):
"""Return the squared value."""
return self * self
def sqrt(self):
"""Returns the square root of the value or None if the value is a
quadratic non-residue mod p."""
if self.is_qnr:
return None
if (self._modulus % 4) == 3:
root = self ** ((self._modulus + 1) // 4)
assert(root * root == self)
else:
root = self._tonelli_shanks_sqrt()
if (int(root) & 1) == 0:
return (root, -root)
else:
return (-root, root)
def quartic_root(self):
"""Returns the quartic root of the value or None if no such value
explicitly exists mod p."""
root = self.sqrt()
if root is not None:
r1 = root[0].sqrt() or list()
r2 = root[1].sqrt() or list()
for candidate in list(r1) + list(r2):
if (candidate ** 4) == self:
return candidate
def __checktype(self, value):
if isinstance(value, int):
return value
elif isinstance(value, FieldElement):
if value.modulus == self.modulus:
return int(value)
else:
raise Exception("Cannot perform meaningful arithmetic operations on field elements in different fields.")
def sigint(self):
"""Returns a signed integer if the negative value is less than 10
decimal digits and the absolute negated value is smaller than the
absolute positive value."""
neg = abs(int(-self))
if (neg < int(self)) and (neg < 1000000000):
return -neg
else:
return int(self)
@classmethod
def any_qnr(cls, modulus):
"""Returns any quadratic non-residue in F(modulus)."""
for i in range(1000):
candidate = cls(random.randint(2, modulus - 1), modulus)
if candidate.is_qnr:
return candidate
raise Exception("Could not find a QNR in F_%d with a reasonable amount of tries." % (modulus))
def __int__(self):
return self._intvalue
def __add__(self, value):
value = self.__checktype(value)
if value is None:
return NotImplemented
return FieldElement(int(self) + value, self.modulus)
def __sub__(self, value):
value = self.__checktype(value)
if value is None:
return NotImplemented
return FieldElement(int(self) - value, self.modulus)
def __mul__(self, value):
value = self.__checktype(value)
if value is None:
return NotImplemented
return FieldElement(int(self) * value, self.modulus)
def __floordiv__(self, value):
value = self.__checktype(value)
if value is None:
return NotImplemented
return self * FieldElement(value, self.modulus).inverse()
def __pow__(self, exponent):
assert(isinstance(exponent, int))
return FieldElement(pow(int(self), exponent, self.modulus), self.modulus)
def __neg__(self):
return FieldElement(-int(self), self.modulus)
def __radd__(self, value):
return self + value
def __rsub__(self, value):
return -self + value
def __rmul__(self, value):
return self * value
def __rfloordiv__(self, value):
return self.inverse() * value
def __eq__(self, value):
if value is None:
return False
value = self.__checktype(value)
return int(self) == (value % self.modulus)
def __ne__(self, other):
return not (self == other)
def __lt__(self, value):
value = self.__checktype(value)
return int(self) < value
def __hash__(self):
return hash((self._intvalue, self._modulus))
def __repr__(self):
return str(self)
def __str__(self):
return "{0x%x}" % (int(self))

164
toyecc/MontgomeryCurve.py Normal file
View File

@ -0,0 +1,164 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
import collections
from .FieldElement import FieldElement
from .AffineCurvePoint import AffineCurvePoint
from .EllipticCurve import EllipticCurve
from .DocInherit import doc_inherit
import toyecc.TwistedEdwardsCurve
_MontgomeryCurveDomainParameters = collections.namedtuple("MontgomeryCurveDomainParameters", [ "curvetype", "a", "b", "p", "n", "G" ])
class MontgomeryCurve(EllipticCurve):
"""Represents an elliptic curve over a finite field F_P that satisfies the
Montgomery equation by^2 = x^3 + ax^2 + x."""
pretty_name = "Montgomery"
def __init__(self, a, b, p, n, h, Gx, Gy, **kwargs):
"""Create an elliptic Montgomery curve given the equation coefficients
a and b, the curve modulus p, the order of the curve n, the cofactor of
the curve h and the generator point G's X and Y coordinates in affine
representation, Gx and Gy."""
EllipticCurve.__init__(self, p, n, h, Gx, Gy, **kwargs)
assert(isinstance(a, int)) # Curve coefficent A
assert(isinstance(b, int)) # Curve coefficent B
self._a = FieldElement(a, p)
self._b = FieldElement(b, p)
self._name = kwargs.get("name")
# Check that the curve is not singular
assert(self.b * ((self.a ** 2) - 4) != 0)
if self._G is not None:
# Check that the generator G is on the curve
assert(self._G.oncurve())
# Check that the generator G is of curve order
assert((self.n * self.G).is_neutral)
@property
@doc_inherit(EllipticCurve)
def domainparams(self):
return _MontgomeryCurveDomainParameters(curvetype = self.curvetype, a = self.a, b = self.b, p = self.p, n = self.n, G = self.G)
@property
@doc_inherit(EllipticCurve)
def curvetype(self):
return "montgomery"
@property
def a(self):
"""Returns the coefficient a of the curve equation by^2 = x^3 + ax^2 + x."""
return self._a
@property
def b(self):
"""Returns the coefficient b of the curve equation by^2 = x^3 + ax^2 + x."""
return self._b
@doc_inherit(EllipticCurve)
def oncurve(self, P):
return (P.is_neutral) or ((self.b * P.y ** 2) == (P.x ** 3) + (self.a * (P.x ** 2)) + P.x)
@doc_inherit(EllipticCurve)
def point_conjugate(self, P):
return AffineCurvePoint(int(P.x), int(-P.y), self)
@doc_inherit(EllipticCurve)
def point_addition(self, P, Q):
if P.is_neutral:
# P is at infinity, O + Q = Q
result = Q
elif P == -Q:
# P == -Q, return O (point at infinity)
result = AffineCurvePoint.neutral(self)
elif P == Q:
# P == Q, point doubling
newx = -2 * P.x - self.a + (3 * P.x**2 + 2 * P.x * self.a + 1)**2 // (4 * P.y**2 * self.b)
newy = -P.y + (3 * P.x**2 + 2 * P.x * self.a + 1) * (3 * P.x + self.a) // (2 * P.y * self.b) - (3 * P.x**2 + 2 * P.x * self.a + 1)**3 // (8 * P.y**3 * self.b**2)
result = AffineCurvePoint(int(newx), int(newy), self)
else:
# P != Q, point addition
newx = -P.x - Q.x - self.a + (P.y - Q.y)**2 * self.b // (P.x - Q.x)**2
newy = (2 * P.x + Q.x + self.a) * (P.y - Q.y) // (P.x - Q.x) - P.y - (P.y - Q.y)**3 * self.b // (P.x - Q.x)**3
result = AffineCurvePoint(int(newx), int(newy), self)
return result
def to_twistededwards(self, a = None):
"""Converts the domain parameters of this curve to domain parameters of
a birationally equivalent twisted Edwards curve. The user may select a
desired a coefficient that the resulting Edwards curve shall have or
leave it at None to accept an arbitrary one."""
assert((a is None) or isinstance(a, int))
# For the Montgomery curve, B can always be arbitrarily chosen as long
# as the surrogate B coeffients are identical in their quadratic
# residue property mod p. This means an Montgomery curve where B is a
# quadratic residue mod p is isomorphous to all other Montgomery curves
# with identical A, p and where B is also a quadratic residue mod p. We
# use this property to get the curve we want if there is a desired "a"
# outcome and choose B appropriately.
if a is None:
# No special wish for a, just do the normal conversion
conversion_b = self.b
a = (self.a + 2) // conversion_b
else:
# We desire a special a and calculate the B we want
conversion_b = (self.a + 2) // a
# And assure that it's QR property is the same as the original
assert(conversion_b.is_qr == self.b.is_qr)
d = (self.a - 2) // conversion_b
# Then construct a curve with no generator first
raw_curve = toyecc.TwistedEdwardsCurve.TwistedEdwardsCurve(
a = int(a),
d = int(d),
p = self.p,
n = self.n,
h = self.h,
Gx = None,
Gy = None,
)
# Convert the generator point to the new curve
G_twed = self.G.convert(raw_curve)
# And recreate the curve with this new generator
twed_curve = toyecc.TwistedEdwardsCurve.TwistedEdwardsCurve(
a = int(a),
d = int(d),
p = self.p,
n = self.n,
h = self.h,
Gx = int(G_twed.x),
Gy = int(G_twed.y),
)
return twed_curve
def __str__(self):
if self.hasname:
return "MontgomeryCurve<%s>" % (self.name)
else:
return "MontgomeryCurve<0x%x y^2 = x^3 + 0x%x x^2 + x mod 0x%x>" % (int(self.b), int(self.a), int(self.p))

258
toyecc/PointOps.py Normal file
View File

@ -0,0 +1,258 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
from . import Tools
from .FieldElement import FieldElement
from .Exceptions import UnsupportedPointFormatException
class PointOpEDDSAEncoding(object):
def eddsa_encode(self):
"""Performs serialization of the point as required by EdDSA."""
coordlen = (self.curve.B + 7) // 8
bitlen = (coordlen * 8) - 1
enc_value = int(self.y)
enc_value &= ((1 << bitlen) - 1)
enc_value |= (int(self.x) & 1) << bitlen
return Tools.inttobytes_le(enc_value, (self.curve.B + 7) // 8)
@staticmethod
def __eddsa_recoverx(curve, y):
x = 0
xx = (y * y - 1) // (curve.d * y * y - curve.a)
if curve.p % 8 == 5:
x = xx ** ((curve.p + 3) // 8)
if x * x == -xx:
x = x * (FieldElement(2, curve.p) ** ((curve.p - 1) // 4))
elif curve.p % 4 == 3:
x = xx ** ((curve.p + 1) // 4)
if x * x != xx:
x = 0
return int(x)
@classmethod
def eddsa_decode(cls, curve, data):
"""Performs deserialization of the point as required by EdDSA."""
assert(curve.curvetype == "twistededwards")
coordlen = (curve.B + 7) // 8
bitlen = (coordlen * 8) - 1
enc_value = int.from_bytes(data, byteorder = "little")
y = enc_value & ((1 << bitlen) - 1)
x = PointOpEDDSAEncoding.__eddsa_recoverx(curve, y)
hibit = (enc_value >> bitlen) & 1
if (x & 1) != hibit:
x = curve.p - x
return cls(x, y, curve)
class PointOpCurveConversion(object):
@staticmethod
def __pconv_twed_mont_scalefactor(twedcurve, montcurve):
native_b = 4 // (twedcurve.a - twedcurve.d)
if native_b == montcurve.b:
# Scaling is not necessary, already native curve format
scale_factor = 1
else:
# Scaling of montgomery y component (v) is needed
if twedcurve.hasgenerator and montcurve.hasgenerator:
# Convert the generator point of the twisted edwards source
# point to unscaled Montgomery space
Gv = (1 + twedcurve.G.y) // ((1 - twedcurve.G.y) * twedcurve.G.x)
# And calculate a multiplicative scaling factor so that the
# point will result in the target curve's generator point Y
scale_factor = montcurve.G.y // Gv
elif native_b.is_qr:
# If b is a quadradic residue mod p then any other
# quadratic residue can serve as a surrgate b coefficient
# to yield an isomorphous curve. Only y coordinate of the
# resulting points needs to be scaled. Calculate a scaling
# ratio.
scale_factors = (montcurve.b // native_b).sqrt()
# At least one of the curves lacks a generator point,
# select just any scale factor
scale_factor = scale_factors[0].inverse()
else:
# Native B is a quadratic non-residue module B; Not sure
# how to handle this case
# TODO: Implement this
raise Exception(NotImplemented)
return scale_factor
def convert(self, targetcurve):
"""Convert the affine curve point to a point on a birationally
equivalent target curve."""
if self.is_neutral:
return targetcurve.neutral()
if (self.curve.curvetype == "twistededwards") and (targetcurve.curvetype == "montgomery"):
# (x, y) are Edwards coordinates
# (u, v) are Montgomery coordonates
(x, y) = (self.x, self.y)
u = (1 + y) // (1 - y)
v = (1 + y) // ((1 - y) * x)
# Montgomery coordinates are unscaled to the actual B coefficient
# of the curve right now. Calculate scaling factor and scale v
# appropriately
scaling_factor = self.__pconv_twed_mont_scalefactor(self.curve, targetcurve)
v = v * scaling_factor
point = self.__class__(int(u), int(v), targetcurve)
elif (self.curve.curvetype == "montgomery") and (targetcurve.curvetype == "twistededwards"):
# (x, y) are Edwards coordinates
# (u, v) are Montgomery coordonates
(u, v) = (self.x, self.y)
y = (u - 1) // (u + 1)
x = -(1 + y) // (v * (y - 1))
# Twisted Edwards coordinates are unscaled to the actual B
# coefficient of the curve right now. Calculate scaling factor and
# scale x appropriately
scaling_factor = self.__pconv_twed_mont_scalefactor(targetcurve, self.curve)
x = x * scaling_factor
point = self.__class__(int(x), int(y), targetcurve)
else:
raise Exception(NotImplemented)
assert(point.oncurve())
return point
class PointOpNaiveOrderCalculation(object):
def naive_order_calculation(self):
"""Calculates the order of the point naively, i.e. by walking through
all points until the given neutral element is hit. Note that this only
works for smallest of curves and is not computationally feasible for
anything else."""
curpt = self
order = 1
while not curpt.is_neutral:
order += 1
curpt += self
return order
class PointOpSerialization(object):
def serialize_uncompressed(self):
"""Serializes the point into a bytes object in uncompressed form."""
length = (self.curve.p.bit_length() + 7) // 8
serialized = bytes([ 0x04 ]) + Tools.inttobytes(int(self.x), length) + Tools.inttobytes(int(self.y), length)
return serialized
@classmethod
def deserialize_uncompressed(cls, data, curve = None):
"""Deserializes a curve point which is given in uncompressed form. A
curve may be passed with the 'curve' argument in which case an
AffineCurvePoint is returned from this method. Otherwise the affine X
and Y coordinates are returned as a tuple."""
if data[0] != 0x04:
raise UnsupportedPointFormatException("Generator point of explicitly encoded curve is given in unsupported form (0x%x)." % (data[0]))
data = data[1:]
assert((len(data) % 2) == 0)
Px = Tools.bytestoint(data[ : len(data) // 2])
Py = Tools.bytestoint(data[len(data) // 2 : ])
if curve is not None:
return cls(Px, Py, curve)
else:
return (Px, Py)
class PointOpScalarMultiplicationXOnly():
"""Compute an X-only ladder scalar multiplication of the private key and
the X coordinate of a given point."""
def _x_double(self, x):
"""Doubling of point with coordinate x."""
if x is None:
return None
den = 4 * (x**3 + self.curve.a * x + self.curve.b)
if den == 0:
# Point at infinity
return None
num = (x**2 - self.curve.a)**2 - (8 * self.curve.b * x)
return num // den
def _x_add_multiplicative(self, x1, x2, x3prime):
"""Multiplicative formula addition of x1 + x2, where x3' is the
difference in X of P1 - P2. Using this function only makes sense where
(P1 - P2) is fixed, as it is in the ladder implementation."""
if x1 is None:
return x2
elif x2 is None:
return x1
elif x1 == x2:
return None
num = -4 * self.curve.b * (x1 + x2) + (x1 * x2 - self.curve.a)**2
den = x3prime * (x1 - x2)**2
result = num // den
return result
def _x_add_additive(self, x1, x2, x3prime):
"""Additive formula addition of x1 + x2, where x3' is the difference in
X of P1 - P2. Using this function only makes sense where (P1 - P2) is
fixed, as it is in the ladder implementation."""
if x1 is None:
return x2
elif x2 is None:
return x1
elif x1 == x2:
return None
num = 2 * (x1 + x2) * (x1 * x2 + self.curve.a) + 4 * self.curve.b
den = (x1 - x2) ** 2
result = num // den - x3prime
return result
def _x_add(self, x1, x2, x3prime):
"""There are two equivalent implementations, one using the
multiplicative and the other using the additive representation. Both
should work equally well."""
return self._x_add_multiplicative(x1, x2, x3prime)
#return self._x_add_additive(x1, x2, x3prime)
def scalar_mul_xonly(self, scalar):
"""This implements the X-coordinate-only multiplication algorithm of a
Short Weierstrass curve with the X coordinate of a given point.
Reference is "Izu and Takagi: A Fast Parallel Elliptic Curve
Multiplication Resistant against Side Channel Attacks" (2002)"""
if self.curve.curvetype != "shortweierstrass":
raise NotImplementedError("X-only ladder multiplication is only implemented for Short Weierstrass curves")
if self.is_neutral:
# Point at infinity is input
return None
elif scalar == 0:
# Multiplication with zero -> point at infinity is output
return None
x_coordinate = int(self.x)
if not isinstance(x_coordinate, FieldElement):
x_coordinate = FieldElement(x_coordinate, self.curve.p)
Q = [ x_coordinate, self._x_double(x_coordinate), None ]
for bitno in reversed(range(scalar.bit_length() - 1)):
bit = (scalar >> bitno) & 1
Q[2] = self._x_double(Q[bit])
Q[1] = self._x_add(Q[0], Q[1], x_coordinate)
Q[0] = Q[2 - bit]
Q[1] = Q[1 + bit]
return Q[0]

331
toyecc/Polynomial.py Normal file
View File

@ -0,0 +1,331 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
import re
import collections
from .FieldElement import FieldElement
class _CoeffDict(object):
def __init__(self):
self._coeffs = { }
def clone(self):
clone = _CoeffDict()
clone._coeffs = dict(self._coeffs)
return clone
@property
def degree(self):
if len(self._coeffs) > 0:
return max(self._coeffs.keys())
else:
return 0
def clone(self):
clone = _CoeffDict()
clone._coeffs = dict(self._coeffs)
return clone
def __eq__(self, other):
return self._coeffs == other._coeffs
def __neq__(self, other):
return not (self == other)
def __iter__(self):
return iter(self._coeffs.items())
def __len__(self):
return len(self._coeffs)
def __getitem__(self, key):
return self._coeffs.get(key, 0)
def __setitem__(self, key, value):
if (value == 0) and (key in self._coeffs):
del self._coeffs[key]
else:
self._coeffs[key] = value
def __str__(self):
return "CoeffDict<%s>" % (str(self._coeffs))
class Polynomial(object):
_TERM_RE = re.compile("^((?P<coeff>-?\d+)\*)?x(\^(?P<exponent>\d+))?$")
_CACHE_EXPONENTS = [ 2, 3 ]
def __init__(self, modulus, initvalue = None):
self._modulus = modulus
self._terms = _CoeffDict()
if initvalue is None:
self._terms[1] = FieldElement(1, self._modulus)
else:
if initvalue != 0:
self._terms[0] = FieldElement(initvalue, self._modulus)
self._expcache = { }
@property
def degree(self):
return self._terms.degree
@property
def modulus(self):
return self._modulus
@property
def is_constant(self):
return self.degree == 0
def get_constant(self):
assert(self.is_constant)
return self[0]
def _clone(self):
clone = Polynomial(self.modulus, 0)
clone._terms = self._terms.clone()
return clone
def substitute(self, value):
result = 0
for (exponent, coefficient) in self._terms:
result += coefficient * (value ** exponent)
return result
def gcd(self, other):
"""Returns the greatest common divisor polynomial of this object and
the other polynomial."""
assert(isinstance(other, Polynomial))
assert(self.modulus == other.modulus)
assert((self != 0) or (other != 0))
(a, b) = (self, other)
if a == 0:
return b
elif b == 0:
return a
while b != 0:
(a, b) = (b, a % b)
highest_coefficient = a._terms[a.degree]
a = a // highest_coefficient
return a
def __and__(self, other):
"""Returns the greatest common divisor polynomial of this object and
the other polynomial."""
return self.gcd(other)
def __add__(self, value):
if isinstance(value, int) or isinstance(value, FieldElement):
result = self._clone()
result._terms[0] += value
return result
elif isinstance(value, Polynomial):
result = self._clone()
for (exponent, coefficient) in value:
result._terms[exponent] += coefficient
return result
else:
raise Exception(NotImplemented)
def __sub__(self, value):
if isinstance(value, int) or isinstance(value, FieldElement):
result = self._clone()
result._terms[0] -= value
return result
elif isinstance(value, Polynomial):
result = self._clone()
for (exponent, coefficient) in value:
result._terms[exponent] -= coefficient
return result
else:
raise Exception(NotImplemented)
def __pow__(self, value):
if value in self._expcache:
return self._expcache[value]
if isinstance(value, int):
if len(self._terms) == 1:
result = Polynomial(self.modulus, 0)
for (exponent, coefficient) in self:
result._terms[exponent * value] = coefficient ** value
else:
exponent = value
result = Polynomial(self.modulus, 1)
multiplier = self
for bit in range(exponent.bit_length()):
if exponent & (1 << bit):
result = result * multiplier
multiplier = multiplier * multiplier
else:
raise Exception(NotImplemented)
if value in self._CACHE_EXPONENTS:
self._expcache[value] = result
return result
def powmod(self, exponent, modulus):
"""Returns the result of (self^exponent) % modulus. Exponent must be an
integer and modulus another Polynomial."""
assert(isinstance(exponent, int))
assert((modulus is None) or isinstance(modulus, Polynomial))
assert(exponent >= 0)
result = Polynomial(self.modulus, 1)
multiplier = self
for bit in range(exponent.bit_length()):
if exponent & (1 << bit):
result = (result * multiplier) % modulus
multiplier = (multiplier * multiplier) % modulus
return result
@classmethod
def parse_poly(cls, polystr, modulus):
poly = Polynomial(modulus, 0)
polystr = polystr.replace(" - ", " + -")
terms = polystr.split(" + ")
for term in terms:
if term.isnumeric():
poly._terms[0] += int(term)
else:
result = cls._TERM_RE.match(term)
if result is None:
raise Exception("Cannot parse polynomial term: '%s'" % (term))
result = result.groupdict()
result = { key: int(value) for (key, value) in result.items() if (value is not None) }
coeff = result.get("coeff", 1)
exponent = result.get("exponent", 1)
poly._terms[exponent] += FieldElement(coeff, modulus)
return poly
def __floordiv__(self, value):
if isinstance(value, int) or isinstance(value, FieldElement):
result = Polynomial(self.modulus, 0)
for (exponent, coefficient) in self:
result._terms[exponent] = coefficient // value
return result
elif isinstance(value, Polynomial):
if value.degree == 0:
return self // value[0]
result = Polynomial(self.modulus, 0)
numerator = self._clone()
while numerator.degree >= value.degree:
shift = numerator.degree - value.degree
multiplier = numerator[numerator.degree] // value[value.degree]
result._terms[shift] += multiplier
for (exponent, coefficient) in value:
numerator._terms[exponent + shift] -= multiplier * coefficient
return result
else:
raise Exception(NotImplemented)
def __mul__(self, value):
if isinstance(value, int) or isinstance(value, FieldElement):
result = Polynomial(self.modulus, 0)
for (exponent, coefficient) in self:
result._terms[exponent] = coefficient * value
return result
elif isinstance(value, Polynomial):
result = Polynomial(self.modulus, 0)
for (exponent1, coefficient1) in self:
for (exponent2, coefficient2) in value:
result._terms[exponent1 + exponent2] += coefficient1 * coefficient2
return result
else:
raise Exception(NotImplemented)
def __mod__(self, value):
if isinstance(value, Polynomial):
if value.degree == 0:
return Polynomial(self.modulus, 0)
result = self._clone()
while result.degree >= value.degree:
shift = result.degree - value.degree
multiplier = result[result.degree] // value[value.degree]
for (exponent, coefficient) in value:
result._terms[exponent + shift] -= multiplier * coefficient
return result
else:
raise Exception(NotImplemented)
def __rmul__(self, value):
return self * value
def __radd__(self, value):
return self + value
def __getitem__(self, exponent):
return self._terms[exponent]
def __iter__(self):
yield from iter(self._terms)
def __eq__(self, value):
if isinstance(value, int) or isinstance(value, FieldElement):
return self.is_constant and (self.get_constant() == value)
elif isinstance(value, Polynomial):
return (self.modulus == value.modulus) and (self._terms == value._terms)
else:
raise Exception(NotImplemented)
def __ne__(self, value):
return not (self == value)
def __repr__(self):
return str(self)
def __str__(self):
terms = [ ]
for (exponent, coefficient) in sorted(self, reverse = True):
if coefficient == 0:
continue
if exponent == 0:
terms.append("%d" % (int(coefficient)))
continue
elif coefficient == 1:
coeffstr = ""
else:
coeffstr = "%d*" % (int(coefficient))
if exponent == 1:
termstr = "x"
else:
termstr = "x^%d" % (int(exponent))
terms.append(coeffstr + termstr)
if len(terms) == 0:
return "0"
else:
return " + ".join(terms)

228
toyecc/PrivKeyOps.py Normal file
View File

@ -0,0 +1,228 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
import hashlib
import collections
from .FieldElement import FieldElement
from .Random import secure_rand, secure_rand_int_between
from .AffineCurvePoint import AffineCurvePoint
from .CurveDB import CurveDB
from .ShortWeierstrassCurve import ShortWeierstrassCurve
from .ASN1 import parse_asn1_private_key, parse_asn1_field_params_fp
from . import Tools
from .CurveQuirks import CurveQuirkEdDSASetPrivateKeyMSB, CurveQuirkEdDSAEnsurePrimeOrderSubgroup, CurveQuirkSigningHashFunction
class PrivKeyOpECDSASign(object):
ECDSASignature = collections.namedtuple("ECDSASignature", [ "hashalg", "r", "s" ])
def ecdsa_sign_hash(self, message_digest, k = None, digestname = None):
"""Signs a given messagedigest, given as bytes, using ECDSA.
Optionally a nonce k can be supplied which should usually be unqiuely
chosen for every ECDSA signature. This way it is possible to
deliberately create broken signatures which can be exploited later on.
If k is not supplied, it is randomly chosen. If a digestname is
supplied the name of this digest eventually ends up in the
ECDSASignature object."""
assert(isinstance(message_digest, bytes))
assert((k is None) or isinstance(k, int))
# Convert message digest to integer value
e = Tools.ecdsa_msgdigest_to_int(message_digest, self.curve.n)
# Select a random integer (if None is supplied!)
if k is None:
k = secure_rand_int_between(1, self.curve.n - 1)
# r = (k * G)_x mod n
Rmodp = k * self.curve.G
r = int(Rmodp.x) % self.curve.n
assert(r != 0)
s = FieldElement(e + self.scalar * r, self.curve.n) // k
return self.ECDSASignature(r = r, s = int(s), hashalg = digestname)
def ecdsa_sign(self, message, digestname, k = None):
"""Signs a given message with the digest that is given as a string.
Optionally a nonce k can be supplied which should usually be unqiuely
chosen for every ECDSA signature. This way it is possible to
deliberately create broken signatures which can be exploited later
on. If k is not supplied, it is randomly chosen."""
assert(isinstance(message, bytes))
assert(isinstance(digestname, str))
digest_fnc = hashlib.new(digestname)
digest_fnc.update(message)
message_digest = digest_fnc.digest()
return self.ecdsa_sign_hash(message_digest, k = k, digestname = digestname)
class PrivKeyOpECIESDecrypt(object):
def ecies_decrypt(self, R):
"""Takes the transmitted point R and reconstructs the shared secret
point S using the private key."""
# Transmitted R is given, restore the symmetric key S
return self._scalar * R
class PrivKeyOpEDDSASign(object):
class EDDSASignature(object):
def __init__(self, curve, R, s):
self._curve = curve
self._R = R
self._s = s
@property
def curve(self):
return self._curve
@property
def R(self):
return self._R
@property
def s(self):
return self._s
def encode(self):
"""Performs serialization of the signature as used by EdDSA."""
return self.R.eddsa_encode() + Tools.inttobytes_le(self.s, (self.curve.B + 7) // 8)
@classmethod
def decode(cls, curve, encoded_signature):
"""Performs deserialization of the signature as used by EdDSA."""
assert(isinstance(encoded_signature, bytes))
coordlen = (curve.B + 7) // 8
assert(len(encoded_signature) == 2 * coordlen)
encoded_R = encoded_signature[:coordlen]
encoded_s = encoded_signature[coordlen:]
R = AffineCurvePoint.eddsa_decode(curve, encoded_R)
s = Tools.bytestoint_le(encoded_s)
return cls(curve, R, s)
def __eq__(self, other):
return (self.R, self.s) == (other.R, other.s)
def __str__(self):
return "EDDSASignature<R = %s, s = %s>" % (self.R, self.s)
def eddsa_sign(self, message):
"""Performs an EdDSA signature of the message. For this to work the
curve has to be a twisted Edwards curve and the private key scalar has
to be generated from a hashed seed. This hashed seed is automatically
generated when a keypair is generated using, for example, the
eddsa_generate() function instead of the regular key generation
function generate()."""
assert(self.curve.curvetype == "twistededwards")
if self._seed is None:
raise Exception("EdDSA requires a seed which is the source for calculation of the private key scalar.")
if not self.curve.has_quirk(CurveQuirkSigningHashFunction):
raise Exception("Unable to determine EdDSA signature function.")
quirk = self.curve.get_quirk(CurveQuirkSigningHashFunction)
h = quirk.hashdata(self._seed)
coordlen = (self.curve.B + 7) // 8
r = Tools.bytestoint_le(quirk.hashdata(h[coordlen : 2 * coordlen] + message))
R = r * self.curve.G
s = (r + Tools.bytestoint_le(quirk.hashdata(R.eddsa_encode() + self.pubkey.point.eddsa_encode() + message)) * self.scalar) % self.curve.n
sig = self.EDDSASignature(self.curve, R, s)
return sig
class PrivKeyOpEDDSAKeyGen(object):
@classmethod
def eddsa_generate(cls, curve, seed = None):
"""Generates a randomly selected seed value. This seed value is then
hashed using the EdDSA hash function (SHA512 for Ed2556 and
Shake256-114 for Ed448) and the resulting value is (slightly modified)
used as the private key scalar. Since for EdDSA signing operations
this seed value is needed, it is also stored within the private key."""
coordlen = (curve.B + 7) // 8
if seed is None:
seed = secure_rand(coordlen)
assert(isinstance(seed, bytes))
assert(len(seed) == coordlen)
# Calculate hash over seed and generate scalar from hash over seed
if not curve.has_quirk(CurveQuirkSigningHashFunction):
raise Exception("Unable to determine EdDSA signature function.")
quirk = curve.get_quirk(CurveQuirkSigningHashFunction)
h = quirk.hashdata(seed)
a = int.from_bytes(h[:coordlen], byteorder = "little") & ((1 << (curve.B - 1)) - 1)
# Do we need to mask out lower significant bits to ensure that we use a
# prime order subgroup?
if curve.has_quirk(CurveQuirkEdDSAEnsurePrimeOrderSubgroup):
if not Tools.is_power_of_two(curve.h):
raise Exception("Can only ensure prime order subgroup by masking 'a' when curve cofactor is a power of two, h = %d isn't." % (curve.h))
a &= ~(curve.h - 1)
# Is the MSB of the curve always set to ensure constant runtime of the
# Montgomery ladder?
if curve.has_quirk(CurveQuirkEdDSASetPrivateKeyMSB):
bit = curve.n.bit_length() + 1
a |= (1 << bit)
privkey = cls(a, curve)
privkey.set_seed(seed)
return privkey
class PrivKeyOpEDDSAEncode(object):
def eddsa_encode(self):
"""Performs serialization of a private key that is used for EdDSA."""
return self.seed
@classmethod
def eddsa_decode(cls, curve, encoded_privkey):
"""Performs decoding of a serialized private key as it is used for EdDSA."""
return cls.eddsa_generate(curve, encoded_privkey)
class PrivKeyOpECDH(object):
def ecdh_compute(self, peer_pubkey):
"""Compute the shared secret point using our own private key and the
public key of our peer."""
return self.scalar * peer_pubkey.point
class PrivKeyOpLoad(object):
@classmethod
def load_derdata(cls, derdata):
"""Loads an EC private key from a DER-encoded ASN.1 bytes object."""
asn1 = parse_asn1_private_key(derdata)
private_key_scalar = Tools.bytestoint(asn1["privateKey"])
curve = CurveDB().get_curve_from_asn1(asn1["parameters"])
return cls(private_key_scalar, curve)
@classmethod
def load_pem(cls, pemfilename):
"""Loads an EC private key from a PEM-encoded 'EC PRIVATE KEY' file."""
return cls.load_derdata(Tools.load_pem_data(pemfilename, "EC PRIVATE KEY"))
@classmethod
def load_der(cls, derfilename):
"""Loads an EC private key from a DER-encoded ASN.1 file."""
with open(derfilename, "rb") as f:
data = f.read()
return cls.load_derdata(data)

166
toyecc/PubKeyOps.py Normal file
View File

@ -0,0 +1,166 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
import hashlib
from .FieldElement import FieldElement
from .AffineCurvePoint import AffineCurvePoint
from .Random import secure_rand_int_between
from . import Tools
from .PrivKeyOps import PrivKeyOpLoad
from .ASN1 import parse_asn1_public_key
from .CurveDB import CurveDB
from .CurveQuirks import CurveQuirkSigningHashFunction
class PubKeyOpECDSAExploitReusedNonce(object):
def ecdsa_exploit_reused_nonce(self, msg1, sig1, msg2, sig2):
"""Given two different messages msg1 and msg2 and their corresponding
signatures sig1, sig2, try to calculate the private key that was used
for signing if during signature generation no unique nonces were
used."""
assert(isinstance(msg1, bytes))
assert(isinstance(msg2, bytes))
assert(msg1 != msg2)
assert(sig1.r == sig2.r)
# Hash the messages
dig1 = hashlib.new(sig1.hashalg)
dig1.update(msg1)
dig1 = dig1.digest()
dig2 = hashlib.new(sig2.hashalg)
dig2.update(msg2)
dig2 = dig2.digest()
# Calculate hashes of messages
e1 = Tools.ecdsa_msgdigest_to_int(dig1, self.point.curve.n)
e2 = Tools.ecdsa_msgdigest_to_int(dig2, self.point.curve.n)
# Take them modulo n
e1 = FieldElement(e1, self.point.curve.n)
e2 = FieldElement(e2, self.point.curve.n)
(s1, s2) = (FieldElement(sig1.s, self.point.curve.n), FieldElement(sig2.s, self.point.curve.n))
r = sig1.r
# Recover (supposedly) random nonce
nonce = (e1 - e2) // (s1 - s2)
# Recover private key
priv = ((nonce * s1) - e1) // r
return { "nonce": nonce, "privatekey": priv }
class PubKeyOpECDSAVerify(object):
def ecdsa_verify_hash(self, message_digest, signature):
"""Verify ECDSA signature over the hash of a message (the message
digest)."""
assert(isinstance(message_digest, bytes))
assert(0 < signature.r < self.curve.n)
assert(0 < signature.s < self.curve.n)
# Convert message digest to integer value
e = Tools.ecdsa_msgdigest_to_int(message_digest, self.curve.n)
(r, s) = (signature.r, FieldElement(signature.s, self.curve.n))
w = s.inverse()
u1 = int(e * w)
u2 = int(r * w)
pt = (u1 * self.curve.G) + (u2 * self.point)
x1 = int(pt.x) % self.curve.n
return x1 == r
def ecdsa_verify(self, message, signature):
"""Verify an ECDSA signature over a message."""
assert(isinstance(message, bytes))
digest_fnc = hashlib.new(signature.hashalg)
digest_fnc.update(message)
message_digest = digest_fnc.digest()
return self.ecdsa_verify_hash(message_digest, signature)
class PubKeyOpEDDSAVerify(object):
def eddsa_verify(self, message, signature):
"""Verify an EdDSA signature over a message."""
if not self.curve.has_quirk(CurveQuirkSigningHashFunction):
raise Exception("Unable to determine EdDSA signature function.")
quirk = self.curve.get_quirk(CurveQuirkSigningHashFunction)
h = Tools.bytestoint_le(quirk.hashdata(signature.R.eddsa_encode() + self.point.eddsa_encode() + message))
return (signature.s * self.curve.G) == signature.R + (h * self.point)
class PubKeyOpEDDSAEncode(object):
def eddsa_encode(self):
"""Encodes a EdDSA-encoded public key to its serialized (bytes)
form."""
return self.point.eddsa_encode()
@classmethod
def eddsa_decode(cls, curve, encoded_pubkey):
"""Decodes a EdDSA-encoded public key from its serialized (bytes)
form."""
pubkey = AffineCurvePoint.eddsa_decode(curve, encoded_pubkey)
return cls(pubkey)
class PubKeyOpECIESEncrypt(object):
def ecies_encrypt(self, r = None):
"""Generates a shared secret which can be used to symetrically encrypt
data that only the holder of the corresponding private key can read.
The output are two points, R and S: R is the public point that is
transmitted together with the message while S is the point which
resembles the shared secret. The receiver can use R together with her
private key to reconstruct S. A random nonce r can be supplied for this
function. If it isn't supplied, it is randomly chosen."""
# Chose a random number
if r is None:
r = secure_rand_int_between(1, self.curve.n - 1)
R = r * self.curve.G
S = r * self.point
# Return the publicly transmitted R and the symmetric key S
return { "R": R, "S": S }
class PubKeyOpLoad(object):
@classmethod
def load_derdata(cls, derdata):
"""Loads an EC public key from a DER-encoded ASN.1 bytes object."""
asn1 = parse_asn1_public_key(derdata)
curve = CurveDB().get_curve_from_asn1(asn1["algorithm"]["parameters"])
point = AffineCurvePoint.deserialize_uncompressed(Tools.bits_to_bytes(asn1["subjectPublicKey"]), curve)
return cls(point)
@classmethod
def load_pem(cls, pemfilename):
"""Loads an EC public key from a PEM-encoded 'PUBLIC KEY' file."""
return cls.load_derdata(Tools.load_pem_data(pemfilename, "PUBLIC KEY"))
@classmethod
def load_der(cls, derfilename):
"""Loads an EC public key from a DER-encoded ASN.1 file."""
with open(derfilename, "rb") as f:
data = f.read()
return cls.load_derdata(data)

48
toyecc/Random.py Normal file
View File

@ -0,0 +1,48 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
def secure_rand(length):
import os
"""Returns a secure random bytes() object of the length 'length' bytes."""
data = os.urandom(length)
assert(len(data) == length)
return data
def secure_rand_int(max_value):
"""Yields a value 0 <= return < maxvalue."""
assert(max_value >= 2)
bytecnt = ((max_value - 1).bit_length() + 7) // 8
max_bin_value = 256 ** bytecnt
wholecnt = max_bin_value // max_value
cutoff = wholecnt * max_value
while True:
rnd = sum((value << (8 * bytepos)) for (bytepos, value) in enumerate(secure_rand(bytecnt)))
if rnd < cutoff:
break
return rnd % max_value
def secure_rand_int_between(min_value, max_value):
"""Yields a random number which goes from min_value (inclusive) to
max_value (inclusive)."""
return secure_rand_int(max_value - min_value + 1) + min_value

View File

@ -0,0 +1,203 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2016 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
import collections
from .FieldElement import FieldElement
from .AffineCurvePoint import AffineCurvePoint
from .EllipticCurve import EllipticCurve
from .DocInherit import doc_inherit
from .CurveOps import CurveOpIsomorphism, CurveOpExportSage
_ShortWeierstrassCurveDomainParameters = collections.namedtuple("ShortWeierstrassCurveDomainParameters", [ "curvetype", "a", "b", "p", "n", "h", "G" ])
class ShortWeierstrassCurve(EllipticCurve, CurveOpIsomorphism, CurveOpExportSage):
"""Represents an elliptic curve over a finite field F_P that satisfies the
short Weierstrass equation y^2 = x^3 + ax + b."""
pretty_name = "Short Weierstrass"
def __init__(self, a, b, p, n, h, Gx, Gy, **kwargs):
"""Create an elliptic curve given the equation coefficients a and b,
the curve modulus p, the order of the curve n, the cofactor of the
curve h and the generator point G's X and Y coordinates in affine
representation, Gx and Gy."""
EllipticCurve.__init__(self, p, n, h, Gx, Gy, **kwargs)
assert(isinstance(a, int)) # Curve coefficent A
assert(isinstance(b, int)) # Curve coefficent B
self._a = FieldElement(a, p)
self._b = FieldElement(b, p)
self._name = kwargs.get("name")
# Check that the curve is not singular
assert((4 * (self.a ** 3)) + (27 * (self.b ** 2)) != 0)
if self._G is not None:
# Check that the generator G is on the curve
assert(self._G.oncurve())
if self.n is not None:
# Check that the generator G is of curve order if a order was
# passed as well
assert((self.n * self.G).is_neutral)
@classmethod
def init_rawcurve(cls, a, b, p):
"""Returns a raw curve which has an undiscovered amount of points
#E(F_p) (i.e. the domain parameters n and h are not set). This function
can be used to create a curve which is later completed by counting
#E(F_p) using Schoof's algorithm."""
return cls(a = a, b = b, p = p, n = None, h = None, Gx = None, Gy = None)
@property
def is_anomalous(self):
"""Returns if the curve is anomalous, i.e. if #F(p) == p. If this is
the case then there is an efficient method to solve the ECDLP.
Therefore the curve is not suitable for cryptographic use."""
return self.jinv in [ 0, 1728 ]
@property
@doc_inherit(EllipticCurve)
def domainparams(self):
return _ShortWeierstrassCurveDomainParameters(curvetype = self.curvetype, a = self.a, b = self.b, p = self.p, n = self.n, h = self.h, G = self.G)
@property
@doc_inherit(EllipticCurve)
def curvetype(self):
return "shortweierstrass"
@property
def is_koblitz(self):
"""Returns whether the curve allows for efficient computation of a map
\phi in the field (i.e. that the curve is commonly known as a 'Koblitz
Curve'). This corresponds to examples 3 and 4 of the paper "Faster
Point Multiplication on Elliptic Curves with Efficient Endomorphisms"
by Gallant, Lambert and Vanstone."""
return ((self.b == 0) and ((self.p % 4) == 1)) or ((self.a == 0) and ((self.p % 3) == 1))
@property
def security_bit_estimate(self):
"""Returns the bit security estimate of the curve. Subtracts four bits
security margin for Koblitz curves."""
security_bits = self.n.bit_length() // 2
if self.is_koblitz:
security_bits -= 4
return security_bits
@property
@doc_inherit(EllipticCurve)
def prettyname(self):
name = [ ]
name.append(self.pretty_name)
if self.is_koblitz:
name.append("(Koblitz)")
return " ".join(name)
@property
def a(self):
"""Returns the coefficient a of the curve equation y^2 = x^3 + ax + b."""
return self._a
@property
def b(self):
"""Returns the coefficient b of the curve equation y^2 = x^3 + ax + b."""
return self._b
@property
def jinv(self):
"""Returns the j-invariant of the curve, i.e. 1728 * 4 * a^3 / (4 * a^3
+ 27 * b^2)."""
return 1728 * (4 * self.a ** 3) // ((4 * self.a ** 3) + (27 * self.b ** 2))
def getpointwithx(self, x):
"""Returns a tuple of two points which fulfill the curve equation or
None if not such points exist."""
assert(isinstance(x, int))
yy = ((FieldElement(x, self._p) ** 3) + (self._a * x) + self._b)
y = yy.sqrt()
if y:
return (AffineCurvePoint(x, int(y[0]), self), AffineCurvePoint(x, int(y[1]), self))
else:
return None
@doc_inherit(EllipticCurve)
def oncurve(self, P):
return P.is_neutral or ((P.y ** 2) == (P.x ** 3) + (self.a * P.x) + self.b)
@doc_inherit(EllipticCurve)
def point_conjugate(self, P):
return AffineCurvePoint(int(P.x), int(-P.y), self)
@doc_inherit(EllipticCurve)
def point_addition(self, P, Q):
if P.is_neutral:
# P is at infinity, O + Q = Q
result = Q
elif Q.is_neutral:
# Q is at infinity, P + O = P
result = P
elif P == -Q:
# P == -Q, return O (point at infinity)
result = self.neutral()
elif P == Q:
# P == Q, point doubling
s = ((3 * P.x ** 2) + self.a) // (2 * P.y)
newx = s * s - (2 * P.x)
newy = s * (P.x - newx) - P.y
result = AffineCurvePoint(int(newx), int(newy), self)
else:
# P != Q, point addition
s = (P.y - Q.y) // (P.x - Q.x)
newx = (s ** 2) - P.x - Q.x
newy = s * (P.x - newx) - P.y
result = AffineCurvePoint(int(newx), int(newy), self)
return result
@doc_inherit(EllipticCurve)
def compress(self, P):
return (int(P.x), int(P.y) % 2)
@doc_inherit(EllipticCurve)
def uncompress(self, compressed):
(x, ybit) = compressed
x = FieldElement(x, self.p)
alpha = (x ** 3) + (self.a * x) + self.b
(beta1, beta2) = alpha.sqrt()
if (int(beta1) % 2) == ybit:
y = beta1
else:
y = beta2
return AffineCurvePoint(int(x), int(y), self)
@doc_inherit(EllipticCurve)
def enumerate_points(self):
yield self.neutral()
for x in range(self.p):
points = self.getpointwithx(x)
if points is not None:
yield points[0]
yield points[1]
def __str__(self):
if self.hasname:
return "ShortWeierstrassCurve<%s>" % (self.name)
else:
return "ShortWeierstrassCurve<y^2 = x^3 + 0x%x x + 0x%x mod 0x%x>" % (int(self.a), int(self.b), int(self.p))

69
toyecc/Singleton.py Normal file
View File

@ -0,0 +1,69 @@
#!/usr/bin/python3
#
# Singleton - Singleton decorator, taken from PEP318
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of jpycommon.
#
# jpycommon is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# jpycommon is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with jpycommon; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
# File UUID a24a2230-7bd4-4737-98ab-8aae62d1bd57
def singleton(cls):
class InnerClass(cls):
_instance = None
def __new__(cls, *args, **kwargs):
if InnerClass._instance is None:
InnerClass._instance = super(InnerClass, cls).__new__(cls, *args, **kwargs)
InnerClass._instance._initialized = False
return InnerClass._instance
def __init__(self, *args, **kwargs):
if self._initialized:
return
super(InnerClass, self).__init__(*args, **kwargs)
self._initialized = True
InnerClass.__name__ = cls.__name__
return InnerClass
if __name__ == "__main__":
print("start")
@singleton
class FooSingleton():
_barkoo = -1
def __init__(self):
print("init called")
def getid(self):
return id(self) * FooSingleton._barkoo
print("pre init")
x = FooSingleton()
print(x, x.getid())
y = FooSingleton()
print(y, y.getid())
z = FooSingleton()
print(z, z.getid())
assert(x is y)

98
toyecc/Tools.py Normal file
View File

@ -0,0 +1,98 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
import hashlib
import base64
import inspect
def bytestoint_le(data):
"""Converts given bytes to a little-endian integer value."""
return sum(value << (8 * index) for (index, value) in enumerate(data))
def inttobytes_le(value, length):
"""Converts a little-endian integer value into a bytes object."""
return bytes((value >> (8 * i)) & 0xff for i in range(length))
def bytestoint(data):
"""Converts given bytes to a big-endian integer value."""
return bytestoint_le(reversed(data))
def inttobytes(value, length):
"""Converts a big-endian integer value into a bytes object."""
return bytes((value >> (8 * i)) & 0xff for i in reversed(range(length)))
def bits_to_bytes(bitarray):
"""Converts a tuple of bits (e.g. a ASN.1 BitString) to a bytes object.
Only works when number of bits is a multiple of 8."""
def bit_word_to_value(word):
assert(len(word) == 8)
return sum(value << i for (i, value) in enumerate(reversed(word)))
assert((len(bitarray) % 8) == 0)
return bytes(bit_word_to_value(bitarray[i : i + 8]) for i in range(0, len(bitarray), 8))
def ecdsa_msgdigest_to_int(message_digest, curveorder):
"""Performs truncation of a message digest to the bitlength of the curve
order."""
# Convert message digest to integer value
e = bytestoint(message_digest)
# Truncate hash value if necessary
msg_digest_bits = 8 * len(message_digest)
if msg_digest_bits > curveorder.bit_length():
shift = msg_digest_bits - curveorder.bit_length()
e >>= shift
return e
def load_pem_data(filename, specifier):
"""Loads the PEM payload, designated with a BEGIN and END specifier, from a
file given by its filename."""
data = None
with open(filename, "r") as f:
spec_begin = "-----BEGIN " + specifier + "-----"
spec_end = "-----END " + specifier + "-----"
for line in f:
line = line.rstrip()
if (data is None) and (line == spec_begin):
data = [ ]
elif (data is not None) and (line == spec_end):
break
elif data is not None:
data.append(line)
if data is None:
raise Exception("Trying to parse PEM file with specifier '%s', but no such block in file found." % (specifier))
data = base64.b64decode("".join(data).encode("utf-8"))
return data
def is_power_of_two(value):
"""Returns True if the given value is a positive power of two, False
otherwise."""
while value > 0:
if value == 1:
return True
elif (value & 1) == 1:
return False
value >>= 1
return False

View File

@ -0,0 +1,171 @@
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
import collections
from .FieldElement import FieldElement
from .AffineCurvePoint import AffineCurvePoint
from .EllipticCurve import EllipticCurve
from .DocInherit import doc_inherit
import toyecc.MontgomeryCurve
_TwistedEdwardsCurveDomainParameters = collections.namedtuple("TwistedEdwardsCurveDomainParameters", [ "curvetype", "a", "d", "p", "n", "G" ])
class TwistedEdwardsCurve(EllipticCurve):
"""Represents an elliptic curve over a finite field F_P that satisfies the
Twisted Edwards equation a x^2 + y^2 = 1 + d x^2 y^2."""
pretty_name = "Twisted Edwards"
def __init__(self, a, d, p, n, h, Gx, Gy, **kwargs):
"""Create an elliptic Twisted Edwards curve given the equation
coefficients a and d, the curve field's modulus p, the order of the
curve n and the generator point G's X and Y coordinates in affine
representation, Gx and Gy."""
EllipticCurve.__init__(self, p, n, h, Gx, Gy, **kwargs)
assert(isinstance(a, int)) # Curve coefficent A
assert(isinstance(d, int)) # Curve coefficent D
self._a = FieldElement(a, p)
self._d = FieldElement(d, p)
self._name = kwargs.get("name")
# Check that the curve is not singular
assert(self.d * (1 - self.d) != 0)
if self._G is not None:
# Check that the generator G is on the curve
assert(self._G.oncurve())
# Check that the generator G is of curve order
assert((self.n * self.G).is_neutral)
@property
@doc_inherit(EllipticCurve)
def domainparams(self):
return _TwistedEdwardsCurveDomainParameters(curvetype = self.curvetype, a = self.a, d = self.d, p = self.p, n = self.n, G = self.G)
@property
@doc_inherit(EllipticCurve)
def curvetype(self):
return "twistededwards"
@property
def a(self):
"""Returns the coefficient a of the curve equation a x^2 + y^2 = 1 +
d x^2 y^2."""
return self._a
@property
def d(self):
"""Returns the coefficient d of the curve equation a x^2 + y^2 = 1 +
d x^2 y^2."""
return self._d
@property
def B(self):
"""Returns the length of the curve's field modulus in bits plus one."""
return self._p.bit_length() + 1
@property
def is_complete(self):
"""Returns if the twisted Edwards curve is complete. This is the case
exactly when d is a quadratic non-residue modulo p."""
return self.d.is_qnr
@doc_inherit(EllipticCurve)
def neutral(self):
return AffineCurvePoint(0, 1, self)
@doc_inherit(EllipticCurve)
def is_neutral(self, P):
return (P.x == 0) and (P.y == 1)
@doc_inherit(EllipticCurve)
def oncurve(self, P):
return (self.a * P.x ** 2) + P.y ** 2 == 1 + self.d * P.x ** 2 * P.y ** 2
@doc_inherit(EllipticCurve)
def point_conjugate(self, P):
return AffineCurvePoint(int(-P.x), int(P.y), self)
@doc_inherit(EllipticCurve)
def point_addition(self, P, Q):
x = (P.x * Q.y + Q.x * P.y) // (1 + self.d * P.x * Q.x * P.y * Q.y)
y = (P.y * Q.y - self.a * P.x * Q.x) // (1 - self.d * P.x * Q.x * P.y * Q.y)
return AffineCurvePoint(int(x), int(y), self)
def to_montgomery(self, b = None):
"""Converts the twisted Edwards curve domain parameters to Montgomery
domain parameters. For this conversion, b can be chosen semi-freely.
If the native b coefficient is a quadratic residue modulo p, then the
freely chosen b value must also be. If it is a quadratic non-residue,
then so must be the surrogate b coefficient. If b is omitted, the
native b value is used. The generator point of the twisted Edwards
curve is also converted to Montgomery form. For this conversion,
there's an invariant (one of two possible outcomes). An arbitrary
bijection is used for this."""
assert((b is None) or isinstance(b, int))
# Calculate the native montgomery coefficents a, b first
a = 2 * (self.a + self.d) // (self.a - self.d)
native_b = 4 // (self.a - self.d)
if b is None:
b = native_b
else:
# If a b value was supplied, make sure is is either a QR or QNR mod
# p, depending on what the native b value was
b = FieldElement(b, self.p)
if native_b.is_qr != b.is_qr:
raise Exception("The b coefficient of the resulting curve must be a quadratic %s modulo p, %s is not." % ([ "non-residue", "residue" ][native_b.is_qr], str(b)))
# Generate the raw curve without a generator yet
raw_curve = toyecc.MontgomeryCurve.MontgomeryCurve(
a = int(a),
b = int(b),
p = self.p,
n = self.n,
h = self.h,
Gx = None,
Gy = None,
)
# Then convert the original generator point using the raw curve to
# yield a birationally equivalent generator point
G_m = self.G.convert(raw_curve)
# And create the curve again, setting this generator
montgomery_curve = toyecc.MontgomeryCurve.MontgomeryCurve(
a = int(a),
b = int(b),
p = self.p,
n = self.n,
h = self.h,
Gx = int(G_m.x),
Gy = int(G_m.y),
)
return montgomery_curve
def __str__(self):
if self.hasname:
return "TwistedEdwardsCurve<%s>" % (self.name)
else:
return "TwistedEdwardsCurve<0x%x x^2 + y^2 = 1 + 0x%x x^2 y^2 mod 0x%x>" % (int(self.a), int(self.d), int(self.p))

95
toyecc/__init__.py Normal file
View File

@ -0,0 +1,95 @@
"""toyecc - Elliptic Curve Cryptography demonstration library
toyecc is a library that is supposed to demonstrate and teach how Elliptic
Curve Cryptography (ECC) works. It is implemented in pure Python and neither
aims to be feature-complete not side-channel resistant nor secure in any way.
Please keep this in mind when using toyecc. Certain algorithms have been
deliberately implemented exactly without precautions against side-channel
attacks in order to cleanly demonstrate the concepts.
There is a curve database included in toyecc which already knows lots of
interesting elliptic curves by name:
import toyecc
curve = toyecc.getcurvebyname("secp112r1")
print(curve)
ShortWeierstrassCurve<secp112r1>
On this curve you can now create a public/private keypair:
privkey = toyecc.ECPrivateKey.generate(curve)
print(privkey)
PrivateKey<d = 0x89fb9821aa5154c9934b3e0268ef>
print(privkey.pubkey)
PublicKey<(0x69976db41f5e487928463b9f8a38, 0xda1fdba3de89c58683bd2d635430)>
You can also regenerate this keypair later on if you wish to remain it static
and not change with every invocation:
privkey = toyecc.ECPrivateKey(0x89fb9821aa5154c9934b3e0268ef, curve)
print(privkey)
PrivateKey<d = 0x89fb9821aa5154c9934b3e0268ef>
You can use this keypair to perform actions like ECDSA signing:
signature = privkey.ecdsa_sign(b"Foobar", "sha1")
print(signature)
ECDSASignature(hashalg='sha1', r=1762251013383878369191057972852867, s=3691758261134002001156831324480002)
If you want to recreate a public key anew, you can also do so by first creating
the public point:
pubkeypt = toyecc.AffineCurvePoint(0x69976db41f5e487928463b9f8a38, 0xda1fdba3de89c58683bd2d635430, curve)
print(pubkeypt)
(0x69976db41f5e487928463b9f8a38, 0xda1fdba3de89c58683bd2d635430)
Then you can just wrap the point in a ECPublicKey object to have access to
methods like ECDSA verification and such:
pubkey = toyecc.ECPublicKey(pubkeypt)
print(pubkey)
PublicKey<(0x69976db41f5e487928463b9f8a38, 0xda1fdba3de89c58683bd2d635430)>
Lastly, you can verify the signature you created earlier:
pubkey.ecdsa_verify(b"Foobar", signature)
True
And change the message so the signature would become invalid:
pubkey.ecdsa_verify(b"Barfoo", signature)
False
"""
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
from .FieldElement import FieldElement
from .AffineCurvePoint import AffineCurvePoint
from .CurveDB import getcurvedb, getcurveentry, getcurvebyname, getcurvenames
from .ECPrivateKey import ECPrivateKey
from .ECPublicKey import ECPublicKey
from .ShortWeierstrassCurve import ShortWeierstrassCurve
from .CRT import CRT
VERSION = "0.0.9rc0"