commit 82f8dd2c235af86ec9429590d17cb318d99bdc78 Author: zyb Date: Sat Jun 15 07:18:14 2024 +0800 Initial commit diff --git a/.github/workflows/mikrotik_patch.yml b/.github/workflows/mikrotik_patch.yml new file mode 100644 index 0000000..b7670ec --- /dev/null +++ b/.github/workflows/mikrotik_patch.yml @@ -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 + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0540009 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +venv/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..988306e --- /dev/null +++ b/README.md @@ -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). + + + + + diff --git a/install.png b/install.png new file mode 100644 index 0000000..eac84ee Binary files /dev/null and b/install.png differ diff --git a/keygen.exe b/keygen.exe new file mode 100644 index 0000000..c911fa0 Binary files /dev/null and b/keygen.exe differ diff --git a/keygen.png b/keygen.png new file mode 100644 index 0000000..442b5d6 Binary files /dev/null and b/keygen.png differ diff --git a/mikro.py b/mikro.py new file mode 100644 index 0000000..2d8c5b9 --- /dev/null +++ b/mikro.py @@ -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 \ No newline at end of file diff --git a/mikrotik.ico b/mikrotik.ico new file mode 100644 index 0000000..6e31b43 Binary files /dev/null and b/mikrotik.ico differ diff --git a/npk.py b/npk.py new file mode 100644 index 0000000..8b0528d --- /dev/null +++ b/npk.py @@ -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 = '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('bytes: + for part in self._parts: + data_header = struct.pack(' 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() \ No newline at end of file diff --git a/patch.py b/patch.py new file mode 100644 index 0000000..eed1b7e --- /dev/null +++ b/patch.py @@ -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(' '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()) \ No newline at end of file diff --git a/toyecc/ASN1.py b/toyecc/ASN1.py new file mode 100644 index 0000000..8c0a033 --- /dev/null +++ b/toyecc/ASN1.py @@ -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 +# + +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 diff --git a/toyecc/AffineCurvePoint.py b/toyecc/AffineCurvePoint.py new file mode 100644 index 0000000..4a96cea --- /dev/null +++ b/toyecc/AffineCurvePoint.py @@ -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 +# + +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)) diff --git a/toyecc/CRT.py b/toyecc/CRT.py new file mode 100644 index 0000000..e881584 --- /dev/null +++ b/toyecc/CRT.py @@ -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 +# + +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 diff --git a/toyecc/CurveDB.py b/toyecc/CurveDB.py new file mode 100644 index 0000000..d512ce9 --- /dev/null +++ b/toyecc/CurveDB.py @@ -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 +# + +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] diff --git a/toyecc/CurveOps.py b/toyecc/CurveOps.py new file mode 100644 index 0000000..3c66e5d --- /dev/null +++ b/toyecc/CurveOps.py @@ -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 +# + +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 diff --git a/toyecc/CurveQuirks.py b/toyecc/CurveQuirks.py new file mode 100644 index 0000000..4f7fa8f --- /dev/null +++ b/toyecc/CurveQuirks.py @@ -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 +# + +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) diff --git a/toyecc/DivisionPolynomial.py b/toyecc/DivisionPolynomial.py new file mode 100644 index 0000000..bea8c55 --- /dev/null +++ b/toyecc/DivisionPolynomial.py @@ -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 +# + +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)) diff --git a/toyecc/DocInherit.py b/toyecc/DocInherit.py new file mode 100644 index 0000000..165a7f3 --- /dev/null +++ b/toyecc/DocInherit.py @@ -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 +# + +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 diff --git a/toyecc/ECPrivateKey.py b/toyecc/ECPrivateKey.py new file mode 100644 index 0000000..cec62e6 --- /dev/null +++ b/toyecc/ECPrivateKey.py @@ -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 +# + +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" % (self.scalar) + else: + seedstr = "".join("%02x" % (c) for c in self._seed) + return "PrivateKey" % (self.scalar, seedstr) diff --git a/toyecc/ECPublicKey.py b/toyecc/ECPublicKey.py new file mode 100644 index 0000000..0e2e31d --- /dev/null +++ b/toyecc/ECPublicKey.py @@ -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 +# + +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)) diff --git a/toyecc/EllipticCurve.py b/toyecc/EllipticCurve.py new file mode 100644 index 0000000..17324e7 --- /dev/null +++ b/toyecc/EllipticCurve.py @@ -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 +# + +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) diff --git a/toyecc/Exceptions.py b/toyecc/Exceptions.py new file mode 100644 index 0000000..3105156 --- /dev/null +++ b/toyecc/Exceptions.py @@ -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 +# + +class DuplicateCurveException(Exception): + pass + +class NoSuchCurveException(Exception): + pass + +class UnsupportedPointFormatException(Exception): + pass + +class UnsupportedFieldException(Exception): + pass diff --git a/toyecc/FieldElement.py b/toyecc/FieldElement.py new file mode 100644 index 0000000..95d39f9 --- /dev/null +++ b/toyecc/FieldElement.py @@ -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 +# + +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)) diff --git a/toyecc/MontgomeryCurve.py b/toyecc/MontgomeryCurve.py new file mode 100644 index 0000000..34ab3c2 --- /dev/null +++ b/toyecc/MontgomeryCurve.py @@ -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 +# + +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)) diff --git a/toyecc/PointOps.py b/toyecc/PointOps.py new file mode 100644 index 0000000..2c21343 --- /dev/null +++ b/toyecc/PointOps.py @@ -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 +# + +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] diff --git a/toyecc/Polynomial.py b/toyecc/Polynomial.py new file mode 100644 index 0000000..b8d2fc8 --- /dev/null +++ b/toyecc/Polynomial.py @@ -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 +# + +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-?\d+)\*)?x(\^(?P\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) diff --git a/toyecc/PrivKeyOps.py b/toyecc/PrivKeyOps.py new file mode 100644 index 0000000..e2b3c2d --- /dev/null +++ b/toyecc/PrivKeyOps.py @@ -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 +# + +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" % (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) diff --git a/toyecc/PubKeyOps.py b/toyecc/PubKeyOps.py new file mode 100644 index 0000000..00e6b86 --- /dev/null +++ b/toyecc/PubKeyOps.py @@ -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 +# + +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) diff --git a/toyecc/Random.py b/toyecc/Random.py new file mode 100644 index 0000000..74b5715 --- /dev/null +++ b/toyecc/Random.py @@ -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 +# + +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 diff --git a/toyecc/ShortWeierstrassCurve.py b/toyecc/ShortWeierstrassCurve.py new file mode 100644 index 0000000..d84483c --- /dev/null +++ b/toyecc/ShortWeierstrassCurve.py @@ -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 +# + +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" % (int(self.a), int(self.b), int(self.p)) diff --git a/toyecc/Singleton.py b/toyecc/Singleton.py new file mode 100644 index 0000000..fc23186 --- /dev/null +++ b/toyecc/Singleton.py @@ -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 +# +# 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) diff --git a/toyecc/Tools.py b/toyecc/Tools.py new file mode 100644 index 0000000..b6905d0 --- /dev/null +++ b/toyecc/Tools.py @@ -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 +# + +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 diff --git a/toyecc/TwistedEdwardsCurve.py b/toyecc/TwistedEdwardsCurve.py new file mode 100644 index 0000000..8dacfad --- /dev/null +++ b/toyecc/TwistedEdwardsCurve.py @@ -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 +# + +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)) diff --git a/toyecc/__init__.py b/toyecc/__init__.py new file mode 100644 index 0000000..29933ad --- /dev/null +++ b/toyecc/__init__.py @@ -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 + +On this curve you can now create a public/private keypair: + + privkey = toyecc.ECPrivateKey.generate(curve) + print(privkey) + PrivateKey + 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 + +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 +# + +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"