mirror of
https://github.com/elseif/MikroTikPatch.git
synced 2025-01-23 05:25:00 +03:00
Initial commit
This commit is contained in:
commit
82f8dd2c23
139
.github/workflows/mikrotik_patch.yml
vendored
Normal file
139
.github/workflows/mikrotik_patch.yml
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
name: Patch Mikrotik RouterOS
|
||||
on:
|
||||
# push:
|
||||
# branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
Patch_Mikrotik_RouterOS:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TZ: 'Asia/Shanghai'
|
||||
LATEST_STABLE_VERSION_URL: 'https://upgrade.mikrotik.com/routeros/NEWESTa7.stable'
|
||||
LATEST_VERSION: "7.15"
|
||||
CUSTOM_LICENSE_PRIVATE_KEY: ${{ secrets.CUSTOM_LICENSE_PRIVATE_KEY }}
|
||||
CUSTOM_LICENSE_PUBLIC_KEY: ${{ secrets.CUSTOM_LICENSE_PUBLIC_KEY }}
|
||||
CUSTOM_NPK_SIGN_PRIVATE_KEY: ${{ secrets.CUSTOM_NPK_SIGN_PRIVATE_KEY }}
|
||||
CUSTOM_NPK_SIGN_PUBLIC_KEY: ${{ secrets.CUSTOM_NPK_SIGN_PUBLIC_KEY }}
|
||||
MIKRO_LICENSE_PUBLIC_KEY: ${{ secrets.MIKRO_LICENSE_PUBLIC_KEY }}
|
||||
MIKRO_NPK_SIGN_PUBLIC_LKEY: ${{ secrets.MIKRO_NPK_SIGN_PUBLIC_LKEY }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Get latest routeros stable version
|
||||
run: |
|
||||
echo $(uname -a)
|
||||
LATEST_VERSION=$(wget -nv -O - $LATEST_STABLE_VERSION_URL | cut -d ' ' -f1)
|
||||
echo Latest Stabel Version:$LATEST_VERSION
|
||||
echo "LATEST_VERSION=${LATEST_VERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Create keygen
|
||||
run: |
|
||||
zip keygen.zip ./keygen.exe
|
||||
|
||||
- name: Create squashfs for option npk
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
sudo wget -O bash -nv https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox_ASH
|
||||
sudo wget -O busybox -nv https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox
|
||||
sudo chmod +x busybox
|
||||
sudo chmod +x bash
|
||||
sudo mkdir -p ./option-root/bin/
|
||||
sudo mv busybox ./option-root/bin/
|
||||
sudo mv bash ./option-root/bin/
|
||||
COMMANDS=$(./option-root/bin/busybox --list)
|
||||
for cmd in $COMMANDS; do
|
||||
sudo ln -sf /pckg/option/bin/busybox ./option-root/bin/$cmd
|
||||
done
|
||||
sudo rm -f option.sfs
|
||||
sudo mksquashfs option-root option.sfs -quiet -comp xz -no-xattrs -b 256k
|
||||
sudo rm -rf option-root
|
||||
|
||||
- name: Get routeros-${{ env.LATEST_VERSION }}.npk
|
||||
run: sudo wget -nv -O routeros-$LATEST_VERSION.npk https://download.mikrotik.com/routeros/$LATEST_VERSION/routeros-$LATEST_VERSION.npk
|
||||
|
||||
- name: Patch routeros-${{ env.LATEST_VERSION }}.npk
|
||||
run: sudo -E python3 patch.py routeros-$LATEST_VERSION.npk
|
||||
|
||||
- name: Get mikrotik-${{ env.LATEST_VERSION }}.iso
|
||||
run: sudo wget -nv -O mikrotik-$LATEST_VERSION.iso https://download.mikrotik.com/routeros/$LATEST_VERSION/mikrotik-$LATEST_VERSION.iso
|
||||
|
||||
- name: Patch mikrotik-${{ env.LATEST_VERSION }}.iso
|
||||
run: |
|
||||
sudo apt-get install -y mkisofs > /dev/null
|
||||
sudo mkdir ./iso
|
||||
sudo mount -o loop,ro mikrotik-$LATEST_VERSION.iso ./iso
|
||||
sudo mkdir ./new_iso
|
||||
sudo cp -r ./iso/* ./new_iso/
|
||||
sudo rsync -a ./iso/ ./new_iso/
|
||||
sudo umount ./iso
|
||||
sudo rm -rf ./iso
|
||||
sudo rm -f mikrotik-$LATEST_VERSION.iso
|
||||
sudo rm -rf ./new_iso/routeros-$LATEST_VERSION.npk
|
||||
NPK_FILES=$(find ./new_iso/*.npk)
|
||||
for file in $NPK_FILES; do
|
||||
sudo -E python3 npk.py sign $file $file
|
||||
done
|
||||
sudo cp routeros-$LATEST_VERSION.npk ./new_iso/
|
||||
sudo -E python3 npk.py create ./new_iso/gps-$LATEST_VERSION.npk ./option.sfs ./option-$LATEST_VERSION.npk
|
||||
sudo cp option-$LATEST_VERSION.npk ./new_iso/
|
||||
sudo cp linux ./new_iso/isolinux/
|
||||
sudo mkdir ./efiboot
|
||||
sudo mount -o loop ./new_iso/efiboot.img ./efiboot
|
||||
sudo cp linux ./efiboot/linux.x86_64
|
||||
sudo umount ./efiboot
|
||||
sudo rm -rf ./efiboot
|
||||
sudo mkisofs -o mikrotik-$LATEST_VERSION.iso \
|
||||
-V "MikroTik $LATEST_VERSION Patched" \
|
||||
-sysid "" -preparer "MiKroTiK" \
|
||||
-publisher "" -A "MiKroTiK RouterOS" \
|
||||
-b isolinux/isolinux.bin \
|
||||
-c isolinux/boot.cat \
|
||||
-no-emul-boot \
|
||||
-boot-load-size 4 \
|
||||
-boot-info-table \
|
||||
-eltorito-alt-boot \
|
||||
-e efiboot.img \
|
||||
-no-emul-boot \
|
||||
-R \
|
||||
./new_iso
|
||||
sudo rm -rf ./new_iso
|
||||
|
||||
- name: Delete Release tag ${{ env.LATEST_VERSION }}
|
||||
run: |
|
||||
HEADER="Authorization: token ${{ secrets.GITHUB_TOKEN }}"
|
||||
RELEASE_INFO=$(curl -s -H $HEADER https://api.github.com/repos/${{ github.repository }}/releases/tags/${{ env.LATEST_VERSION }})
|
||||
RELEASE_ID=$(echo $RELEASE_INFO | jq -r '.id')
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
if [ "$RELEASE_ID" != "null" ]; then
|
||||
curl -X DELETE -H "$HEADER" https://api.github.com/repos/${{ github.repository }}/git/refs/tags/${{ env.LATEST_VERSION }}
|
||||
echo "Tag ${{ env.LATEST_VERSION }} deleted successfully."
|
||||
curl -X DELETE -H "$HEADER" https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID
|
||||
echo "Release with tag ${{ env.LATEST_VERSION }} deleted successfully."
|
||||
else
|
||||
echo "Release not found for tag: ${{ env.LATEST_VERSION }})"
|
||||
fi
|
||||
|
||||
- name: Create Release tag ${{ env.LATEST_VERSION }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
name: "MikroTik ${{ env.LATEST_VERSION }}"
|
||||
body: "MikroTik ${{ env.LATEST_VERSION }}"
|
||||
tag_name: ${{ env.LATEST_VERSION }}
|
||||
make_latest: "true"
|
||||
files: |
|
||||
mikrotik-${{ env.LATEST_VERSION }}.iso
|
||||
routeros-${{ env.LATEST_VERSION }}.npk
|
||||
option-${{ env.LATEST_VERSION }}.npk
|
||||
keygen.zip
|
||||
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
__pycache__/
|
||||
venv/
|
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Patch MikroTik RouterOS
|
||||
|
||||
### Download [Latest Patched](https://github.com/elseif/MikroTikPatch/releases/latest) iso file,install it and enjoy.
|
||||
|
||||
![](install.png)
|
||||
![](routeros.png)
|
||||
|
||||
### Uses keygen to generate license key.
|
||||
![](keygen.png)
|
||||
### all patches are applied automatically with [github workflow](https://github.com/elseif/MikroTikPatch/blob/main/.github/workflows/mikrotik_patch.yml).
|
||||
|
||||
|
||||
|
||||
|
||||
|
BIN
install.png
Normal file
BIN
install.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
keygen.exe
Normal file
BIN
keygen.exe
Normal file
Binary file not shown.
BIN
keygen.png
Normal file
BIN
keygen.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
213
mikro.py
Normal file
213
mikro.py
Normal file
@ -0,0 +1,213 @@
|
||||
|
||||
import struct
|
||||
from sha256 import SHA256
|
||||
from toyecc import AffineCurvePoint, getcurvebyname, FieldElement,ECPrivateKey,ECPublicKey,Tools
|
||||
from toyecc.Random import secure_rand_int_between
|
||||
|
||||
MIKRO_LICENSE_HEADER = '-----BEGIN MIKROTIK SOFTWARE KEY------------'
|
||||
MIKRO_LICENSE_FOOTER = '-----END MIKROTIK SOFTWARE KEY--------------'
|
||||
MIKRO_BASE64_CHARACTER_TABLE = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
SOFTWARE_ID_CHARACTER_TABLE = b'TN0BYX18S5HZ4IA67DGF3LPCJQRUK9MW2VE'
|
||||
|
||||
MIKRO_SHA256_K = (
|
||||
0x0548D563, 0x98308EAB, 0x37AF7CCC, 0xDFBC4E3C,
|
||||
0xF125AAC9, 0xEC98ACB8, 0x8B540795, 0xD3E0EF0E,
|
||||
0x4904D6E5, 0x0DA84981, 0x9A1F8452, 0x00EB7EAA,
|
||||
0x96F8E3B3, 0xA6CDB655, 0xE7410F9E, 0x8EECB03D,
|
||||
0x9C6A7C25, 0xD77B072F, 0x6E8F650A, 0x124E3640,
|
||||
0x7E53785A, 0xE0150772, 0xC61EF4E0, 0xBC57E5E0,
|
||||
0xC0F9A285, 0xDB342856, 0x190834C7, 0xFBEB7D8E,
|
||||
0x251BED34, 0x0E9F2AAD, 0x256AB901, 0x0A5B7890,
|
||||
0x9F124F09, 0xD84A9151, 0x427AF67A, 0x8059C9AA,
|
||||
0x13EAB029, 0x3153CDF1, 0x262D405D, 0xA2105D87,
|
||||
0x9C745F15, 0xD1613847, 0x294CE135, 0x20FB0F3C,
|
||||
0x8424D8ED, 0x8F4201B6, 0x12CA1EA7, 0x2054B091,
|
||||
0x463D8288, 0xC83253C3, 0x33EA314A, 0x9696DC92,
|
||||
0xD041CE9A, 0xE5477160, 0xC7656BE8, 0x5179FE33,
|
||||
0x1F4726F1, 0x5F393AF0, 0x26E2D004, 0x6D020245,
|
||||
0x85FDF6D7, 0xB0237C56, 0xFF5FBD94, 0xA8B3F534
|
||||
)
|
||||
def mikro_softwareid_decode(software_id:str)->int:
|
||||
assert(isinstance(software_id, str))
|
||||
software_id = software_id.replace('-', '')
|
||||
ret = 0
|
||||
for i in reversed(range(len(software_id))):
|
||||
ret *= len(SOFTWARE_ID_CHARACTER_TABLE)
|
||||
ret += SOFTWARE_ID_CHARACTER_TABLE.index(ord(software_id[i]))
|
||||
return ret
|
||||
|
||||
def mikro_softwareid_encode(id:int)->str:
|
||||
assert(isinstance(id, int))
|
||||
ret = ''
|
||||
for i in range(8):
|
||||
ret += chr(SOFTWARE_ID_CHARACTER_TABLE[id % 0x23])
|
||||
id //= 0x23
|
||||
if i == 3:
|
||||
ret += '-'
|
||||
return ret
|
||||
|
||||
def to32bits(v):
|
||||
return (v + (1 << 32)) % (1 << 32)
|
||||
|
||||
def rotl(n, d):
|
||||
return (n << d) | (n >> (32 - d))
|
||||
|
||||
def mikro_encode(s:bytes)->bytes:
|
||||
s = list(struct.unpack('>' + 'I' * (len(s) // 4), s))
|
||||
for i in reversed(range(16)):
|
||||
s[(i+0) % 4] = to32bits(rotl(s[(i+3) % 4], MIKRO_SHA256_K[i*4+3] & 0x0F) ^ (s[(i+0) % 4] - s[(i+3) % 4]))
|
||||
s[(i+3) % 4] = to32bits(s[(i+3) % 4] + s[(i+1) % 4] + MIKRO_SHA256_K[i*4+3])
|
||||
|
||||
s[(i+1) % 4] = to32bits(rotl(s[(i+2) % 4], MIKRO_SHA256_K[i*4+2] & 0x0F) ^ (s[(i+1) % 4] - s[(i+2) % 4]))
|
||||
s[(i+0) % 4] = to32bits(s[(i+0) % 4] + s[(i+2) % 4] + MIKRO_SHA256_K[i*4+2])
|
||||
|
||||
s[(i+2) % 4] = to32bits(rotl(s[(i+1) % 4], MIKRO_SHA256_K[i*4+1] & 0x0F) ^ (s[(i+2) % 4] - s[(i+1) % 4]))
|
||||
s[(i+1) % 4] = to32bits(s[(i+1) % 4] + s[(i+3) % 4] + MIKRO_SHA256_K[i*4+1])
|
||||
|
||||
s[(i+3) % 4] = to32bits(rotl(s[(i+0) % 4], MIKRO_SHA256_K[i*4+0] & 0x0F) ^ (s[(i+3) % 4] - s[(i+0) % 4]))
|
||||
s[(i+2) % 4] = to32bits(s[(i+2) % 4] + s[(i+0) % 4] + MIKRO_SHA256_K[i*4+0])
|
||||
|
||||
encodedLicensePayload = b''
|
||||
for x in s:
|
||||
encodedLicensePayload += x.to_bytes(4, 'big')
|
||||
return encodedLicensePayload
|
||||
|
||||
def mikro_decode(s:bytes)->bytes:
|
||||
s = list(struct.unpack('>'+'I'*(len(s) // 4), s))
|
||||
for i in range(16):
|
||||
s[(i+2) % 4] = to32bits(s[(i+2) % 4] - s[(i+0) % 4] - MIKRO_SHA256_K[i*4+0])
|
||||
s[(i+3) % 4] = to32bits((rotl(s[(i+0) % 4], MIKRO_SHA256_K[i*4+0] & 0x0F) ^ s[(i+3) % 4]) + s[(i+0) % 4])
|
||||
|
||||
s[(i+1) % 4] = to32bits(s[(i+1) % 4] - s[(i+3) % 4] - MIKRO_SHA256_K[i*4+1])
|
||||
s[(i+2) % 4] = to32bits((rotl(s[(i+1) % 4], MIKRO_SHA256_K[i*4+1] & 0x0F) ^ s[(i+2) % 4]) + s[(i+1) % 4])
|
||||
|
||||
s[(i+0) % 4] = to32bits(s[(i+0) % 4] - s[(i+2) % 4] - MIKRO_SHA256_K[i*4+2])
|
||||
s[(i+1) % 4] = to32bits((rotl(s[(i+2) % 4], MIKRO_SHA256_K[i*4+2] & 0x0F) ^ s[(i+1) % 4]) + s[(i+2) % 4])
|
||||
|
||||
s[(i+3) % 4] = to32bits(s[(i+3) % 4] - s[(i+1) % 4] - MIKRO_SHA256_K[i*4+3])
|
||||
s[(i+0) % 4] = to32bits((rotl(s[(i+3) % 4], MIKRO_SHA256_K[i*4+3] & 0x0F) ^ s[(i+0) % 4]) + s[(i+3) % 4])
|
||||
|
||||
ret = b''
|
||||
for x in s:
|
||||
ret += x.to_bytes(4, 'big')
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def mikro_base64_encode(data:bytes, pad = False)->str:
|
||||
encoded = ''
|
||||
left = 0
|
||||
for i in range(0, len(data)):
|
||||
if left == 0:
|
||||
encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[data[i] & 0x3F])
|
||||
left = 2
|
||||
else:
|
||||
if left == 6:
|
||||
encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[data[i - 1] >> 2])
|
||||
encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[data[i] & 0x3F])
|
||||
left = 2
|
||||
else:
|
||||
index1 = data[i - 1] >> (8 - left)
|
||||
index2 = data[i] << (left)
|
||||
encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[(index1 | index2) & 0x3F])
|
||||
left += 2
|
||||
|
||||
if left != 0:
|
||||
encoded += chr(MIKRO_BASE64_CHARACTER_TABLE[data[len(data) - 1] >> (8 - left)])
|
||||
|
||||
if pad:
|
||||
for i in range(0, (4 - len(encoded) % 4) % 4):
|
||||
encoded += '='
|
||||
|
||||
return encoded
|
||||
def mikro_base64_decode(data:str)->bytes:
|
||||
ret = b""
|
||||
data = data.replace("=", "").encode()
|
||||
left = 0
|
||||
for i in range(0, len(data)):
|
||||
if left == 0:
|
||||
left = 6
|
||||
else:
|
||||
value1 = MIKRO_BASE64_CHARACTER_TABLE.index(data[i - 1]) >> (6 - left)
|
||||
value2 = MIKRO_BASE64_CHARACTER_TABLE.index(data[i]) & (2 ** (8 - left) - 1)
|
||||
value = value1 | (value2 << left)
|
||||
ret += bytes([value])
|
||||
left -= 2
|
||||
return ret
|
||||
|
||||
class MikroSHA256(SHA256):
|
||||
K = MIKRO_SHA256_K
|
||||
INITIAL_STATE = SHA256.State(
|
||||
0x5B653932, 0x7B145F8F, 0x71FFB291, 0x38EF925F,
|
||||
0x03E1AAF9, 0x4A2057CC, 0x4CAF4DD9, 0x643CC9EA
|
||||
)
|
||||
|
||||
def mikro_sha256(data:bytes)->bytes:
|
||||
return MikroSHA256(data).digest()
|
||||
|
||||
def mikro_eddsa_sign(data:bytes,private_key:bytes)->bytes:
|
||||
assert(isinstance(data, bytes))
|
||||
assert(isinstance(private_key, bytes))
|
||||
curve = getcurvebyname('Ed25519')
|
||||
private_key = ECPrivateKey.eddsa_decode(curve,private_key)
|
||||
return private_key.eddsa_sign(data).encode()
|
||||
|
||||
def mikro_eddsa_verify(data:bytes,signature:bytes,public_key:bytes):
|
||||
assert(isinstance(data, bytes))
|
||||
assert(isinstance(signature, bytes))
|
||||
assert(isinstance(public_key, bytes))
|
||||
curve = getcurvebyname('Ed25519')
|
||||
public_key = ECPublicKey.eddsa_decode(curve,public_key)
|
||||
signature = ECPrivateKey.EDDSASignature.decode(curve,signature)
|
||||
return public_key.eddsa_verify(data,signature)
|
||||
|
||||
def mikro_kcdsa_sign(data:bytes,private_key:bytes)->bytes:
|
||||
assert(isinstance(data, bytes))
|
||||
assert(isinstance(private_key, bytes))
|
||||
curve = getcurvebyname('Curve25519')
|
||||
private_key:ECPrivateKey = ECPrivateKey(Tools.bytestoint_le(private_key), curve)
|
||||
public_key:ECPublicKey = private_key.pubkey
|
||||
while True:
|
||||
nonce_secret = secure_rand_int_between(1, curve.n - 1)
|
||||
nonce_point = nonce_secret * curve.G
|
||||
nonce = int(nonce_point.x) % curve.n
|
||||
nonce_hash = mikro_sha256(Tools.inttobytes_le(nonce,32))
|
||||
data_hash = bytearray(mikro_sha256(data))
|
||||
for i in range(16):
|
||||
data_hash[8+i] ^= nonce_hash[i]
|
||||
data_hash[0] &= 0xF8
|
||||
data_hash[31] &= 0x7F
|
||||
data_hash[31] |= 0x40
|
||||
data_hash = Tools.bytestoint_le(data_hash)
|
||||
signature = pow(private_key.scalar, -1, curve.n) * (nonce_secret - data_hash)
|
||||
signature %= curve.n
|
||||
if int((public_key.point * signature + curve.G * data_hash).x) == nonce:
|
||||
return bytes(nonce_hash[:16]+Tools.inttobytes_le(signature,32))
|
||||
|
||||
def mikro_kcdsa_verify(data:bytes, signature:bytes, public_key:bytes)->bool:
|
||||
assert(isinstance(data, bytes))
|
||||
assert(isinstance(signature, bytes))
|
||||
assert(isinstance(public_key, bytes))
|
||||
curve = getcurvebyname('Curve25519')
|
||||
#y^2 = x^3 + ax^2 + x
|
||||
x = FieldElement(Tools.bytestoint_le(public_key), curve.p)
|
||||
YY = ((x**3) + (curve.a * x**2) + x).sqrt()
|
||||
public_keys = []
|
||||
for y in YY:
|
||||
public_keys += [AffineCurvePoint(x, int(y), curve)]
|
||||
|
||||
data_hash = bytearray(mikro_sha256(data))
|
||||
nonce_hash = signature[:16]
|
||||
signature = signature[16:]
|
||||
for i in range(16):
|
||||
data_hash[8+i] ^= nonce_hash[i]
|
||||
data_hash[0] &= 0xF8
|
||||
data_hash[31] &= 0x7F
|
||||
data_hash[31] |= 0x40
|
||||
data_hash = Tools.bytestoint_le(data_hash)
|
||||
signature = Tools.bytestoint_le(signature)
|
||||
for public_key in public_keys:
|
||||
nonce = int((public_key * signature + curve.G * data_hash).x)
|
||||
if mikro_sha256(Tools.inttobytes_le(nonce,32))[:len(nonce_hash)] == nonce_hash:
|
||||
return True
|
||||
return False
|
BIN
mikrotik.ico
Normal file
BIN
mikrotik.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
297
npk.py
Normal file
297
npk.py
Normal file
@ -0,0 +1,297 @@
|
||||
|
||||
import struct,zlib
|
||||
from datetime import datetime
|
||||
from dataclasses import dataclass
|
||||
from enum import IntEnum
|
||||
class NpkPartID(IntEnum):
|
||||
NAME_INFO =0x01 # Package information: name, ver, etc.
|
||||
DESCRIPTION =0x02 # Package description
|
||||
DEPENDENCIES =0x03 # Package Dependencies
|
||||
FILE_CONTAINER =0x04 # Files container zlib 1.2.3
|
||||
INSTALL_SCRIPT =0x07 # Install script
|
||||
UNINSTALL_SCRIPT =0x08 # Uninstall script
|
||||
SIGNATURE =0x09 # Package signature
|
||||
ARCHITECTURE =0x10 # Package architecture (e.g. i386)
|
||||
PKG_CONFLICTS =0x11 # Package conflicts
|
||||
PKG_INFO =0x12
|
||||
FEATURES =0x13
|
||||
PKG_FEATURES =0x14
|
||||
SQUASHFS =0x15 # SquashFS
|
||||
NULL_BLOCK =0X16
|
||||
GIT_COMMIT =0x17 # Git commit
|
||||
CHANNEL =0x18 # Release type (e.g. stable, bugfix)
|
||||
HEADER =0x19
|
||||
|
||||
@dataclass
|
||||
class NpkPartItem:
|
||||
id: NpkPartID
|
||||
data: bytes|object
|
||||
class NpkNameInfo:
|
||||
_format = '<16s4sI12s'
|
||||
def __init__(self,name:str,version:str,build_time=datetime.now(),_unknow=b'\x00'*12):
|
||||
self._name = name[:16].encode().ljust(16,b'\x00')
|
||||
self._version = self.encode_version(version)
|
||||
self._build_time = int(build_time.timestamp())
|
||||
self._unknow = _unknow
|
||||
def serialize(self)->bytes:
|
||||
return struct.pack(self._format, self._name,self._version,self._build_time,self._unknow)
|
||||
@staticmethod
|
||||
def unserialize_from(data:bytes)->'NpkNameInfo':
|
||||
assert len(data) == struct.calcsize(NpkNameInfo._format),'Invalid data length'
|
||||
_name, _version,_build_time,_unknow = struct.unpack_from(NpkNameInfo._format,data)
|
||||
return NpkNameInfo(_name.decode(),NpkNameInfo.decode_version(_version),datetime.fromtimestamp(_build_time),_unknow)
|
||||
def __len__ (self)->int:
|
||||
return struct.calcsize(self._format)
|
||||
@property
|
||||
def name(self)->str:
|
||||
return self._name.decode().strip('\x00')
|
||||
@name.setter
|
||||
def name(self,value:str):
|
||||
self._name = value[:16].encode().ljust(16,b'\x00')
|
||||
@staticmethod
|
||||
def decode_version(value:bytes):
|
||||
revision,build,minor,major = struct.unpack_from('4B',value)
|
||||
if build == 97:
|
||||
build = 'alpha'
|
||||
elif build == 98:
|
||||
build = 'beta'
|
||||
elif build == 99:
|
||||
build = 'rc'
|
||||
elif build == 102:
|
||||
if revision & 0x80:
|
||||
build = 'test'
|
||||
revision &= 0x7f
|
||||
else:
|
||||
build = 'final'
|
||||
else:
|
||||
build = 'unknown'
|
||||
return f'{major}.{minor}.{revision}.{build}'
|
||||
@staticmethod
|
||||
def encode_version(value:str):
|
||||
s = value.split('.')
|
||||
if 4 != len(s) and s[3] in [ 'alpha', 'beta', 'rc','final', 'test']:
|
||||
raise ValueError('Invalid version string')
|
||||
major = int(s[0])
|
||||
minor = int(s[1])
|
||||
revision = int(s[2])
|
||||
if s[3] == 'alpha':
|
||||
build = 97
|
||||
elif s[3] == 'beta':
|
||||
build = 98
|
||||
elif s[3] == 'rc':
|
||||
build = 99
|
||||
elif s[3] == 'final':
|
||||
build = 102
|
||||
revision &= 0x7f
|
||||
else: #'test'
|
||||
build = 102
|
||||
revision |= 0x80
|
||||
return struct.pack('4B',revision,build,minor,major)
|
||||
@property
|
||||
def version(self)->str:
|
||||
return self.decode_version(self._version)
|
||||
@version.setter
|
||||
def version(self,value:str = '7.15.1.final'):
|
||||
self._version = self.encode_version(value)
|
||||
@property
|
||||
def build_time(self):
|
||||
return datetime.fromtimestamp(self._build_time)
|
||||
@build_time.setter
|
||||
def build_time(self,value:datetime):
|
||||
self._build_time = int(value.timestamp())
|
||||
|
||||
class NpkFileContainer:
|
||||
_format = '<BB6sIBBBBIIIH'
|
||||
@dataclass
|
||||
class NpkFileItem:
|
||||
perm: int
|
||||
type: int
|
||||
usr_or_grp: int
|
||||
modify_time: int
|
||||
revision: int
|
||||
rc: int
|
||||
minor: int
|
||||
major: int
|
||||
create_time: int
|
||||
unknow: int
|
||||
name: bytes
|
||||
data: bytes
|
||||
def __init__(self,items:list['NpkFileContainer.NpkFileItem']=None):
|
||||
self._items= items
|
||||
def serialize(self)->bytes:
|
||||
compressed_data = b''
|
||||
compressor = zlib.compressobj()
|
||||
for item in self._items:
|
||||
data = struct.pack(self._format, item.perm,item.type,item.usr_or_grp, item.modify_time,item.revision,item.rc,item.minor,item.major,item.create_time,item.unknow,len(item.data),len(item.name))
|
||||
data += item.name + item.data
|
||||
compressed_data += compressor.compress(data)
|
||||
return compressed_data + compressor.flush()
|
||||
@staticmethod
|
||||
def unserialize_from(data:bytes):
|
||||
items:list['NpkFileContainer.NpkFileItem'] = []
|
||||
decompressed_data = zlib.decompress(data)
|
||||
while len(decompressed_data):
|
||||
offset = struct.calcsize(NpkFileContainer._format)
|
||||
perm,type,usr_or_grp, modify_time,revision,rc,minor,major,create_time,unknow,data_size,name_size= struct.unpack_from(NpkFileContainer._format, decompressed_data)
|
||||
name = decompressed_data[offset:offset+name_size]
|
||||
data = decompressed_data[offset+name_size:offset+name_size+data_size]
|
||||
items.append(NpkFileContainer.NpkFileItem(perm,type,usr_or_grp, modify_time,revision,rc,minor,major,create_time,unknow,name,data))
|
||||
decompressed_data = decompressed_data[offset+name_size+data_size:]
|
||||
return NpkFileContainer(items)
|
||||
|
||||
def __len__ (self)->int:
|
||||
return len(self.serialize())
|
||||
def __getitem__(self,index:int)->'NpkFileContainer.NpkFileItem':
|
||||
return self._items[index]
|
||||
def __iter__(self):
|
||||
for item in self._items:
|
||||
yield item
|
||||
|
||||
|
||||
class NovaPackage:
|
||||
NPK_MAGIC = 0xbad0f11e
|
||||
def __init__(self,data:bytes=b''):
|
||||
self._parts:list[NpkPartItem] = []
|
||||
offset = 0
|
||||
while offset < len(data):
|
||||
part_id,part_size = struct.unpack_from('<HI',data,offset)
|
||||
offset += 6
|
||||
part_data = data[offset:offset+part_size]
|
||||
offset += part_size
|
||||
if part_id == NpkPartID.NAME_INFO:
|
||||
self._parts.append(NpkPartItem(NpkPartID(part_id),NpkNameInfo.unserialize_from(part_data)))
|
||||
# elif part_id == NpkPartID.FILE_CONTAINER:
|
||||
# self._parts.append(NpkPartItem(NpkPartID(part_id),NpkFileContainer.unserialize_from(part_data)))
|
||||
else:
|
||||
self._parts.append(NpkPartItem(NpkPartID(part_id),part_data))
|
||||
|
||||
|
||||
def get_digest(self,hash_fnc)->bytes:
|
||||
for part in self._parts:
|
||||
data_header = struct.pack('<HI',part.id.value,len(part.data))
|
||||
if part.id == NpkPartID.HEADER:
|
||||
continue
|
||||
else:
|
||||
hash_fnc.update(data_header)
|
||||
if part.id == NpkPartID.SIGNATURE:
|
||||
break
|
||||
elif part.data:
|
||||
if isinstance(part.data,bytes):
|
||||
hash_fnc.update(part.data)
|
||||
else:
|
||||
hash_fnc.update(part.data.serialize())
|
||||
return hash_fnc.digest()
|
||||
|
||||
def sign(self,kcdsa_private_key:bytes,eddsa_private_key:bytes):
|
||||
import hashlib
|
||||
from mikro import mikro_kcdsa_sign,mikro_eddsa_sign
|
||||
self[NpkPartID.SIGNATURE].data = b'\0'*(20+48+64)
|
||||
sha1_digest = self.get_digest(hashlib.new('SHA1'))
|
||||
sha256_digest = self.get_digest(hashlib.new('SHA256'))
|
||||
kcdsa_signature = mikro_kcdsa_sign(sha256_digest[:20],kcdsa_private_key)
|
||||
eddsa_signature = mikro_eddsa_sign(sha256_digest,eddsa_private_key)
|
||||
self[NpkPartID.SIGNATURE].data = sha1_digest + kcdsa_signature + eddsa_signature
|
||||
|
||||
def verify(self,kcdsa_public_key:bytes,eddsa_public_key:bytes):
|
||||
import hashlib
|
||||
from mikro import mikro_kcdsa_verify,mikro_eddsa_verify
|
||||
sha1_digest = self.get_digest(hashlib.new('SHA1'))
|
||||
sha256_digest = self.get_digest(hashlib.new('SHA256'))
|
||||
signature = self[NpkPartID.SIGNATURE].data
|
||||
if sha1_digest != signature[:20]:
|
||||
return False
|
||||
if not mikro_kcdsa_verify(sha256_digest[:20],signature[20:68],kcdsa_public_key):
|
||||
return False
|
||||
if not mikro_eddsa_verify(sha256_digest,signature[68:132],eddsa_public_key):
|
||||
return False
|
||||
return True
|
||||
|
||||
def __iter__(self):
|
||||
for part in self._parts:
|
||||
yield part
|
||||
|
||||
def __getitem__(self, id:NpkPartID):
|
||||
for part in self._parts:
|
||||
if part.id == id:
|
||||
return part
|
||||
part = NpkPartItem(id,b'')
|
||||
self._parts.append(part)
|
||||
return part
|
||||
|
||||
def save(self,file):
|
||||
size = 0
|
||||
for part in self._parts:
|
||||
size += 6 + len(part.data)
|
||||
with open(file,'wb') as f:
|
||||
f.write(struct.pack('<II', NovaPackage.NPK_MAGIC, size))
|
||||
for part in self._parts:
|
||||
f.write(struct.pack('<HI',part.id.value ,len(part.data)))
|
||||
if isinstance(part.data,bytes):
|
||||
f.write(part.data)
|
||||
else:
|
||||
f.write(part.data.serialize())
|
||||
|
||||
@staticmethod
|
||||
def load(file):
|
||||
with open(file,'rb') as f:
|
||||
data = f.read()
|
||||
assert int.from_bytes(data[:4],'little') == NovaPackage.NPK_MAGIC, 'Invalid Nova Package Magic'
|
||||
assert int.from_bytes(data[4:8],'little') == len(data) - 8, 'Invalid Nova Package Size'
|
||||
return NovaPackage(data[8:])
|
||||
|
||||
def get_latest_version(channel:str) -> str:
|
||||
import requests
|
||||
response = requests.get(f'https://upgrade.mikrotik.com/routeros/NEWESTa7.{channel}')
|
||||
return response.text.split(' ')[0]
|
||||
|
||||
def create_option_npk(npk_file:str,squashfs_file:str)->NovaPackage:
|
||||
|
||||
return option_npk
|
||||
|
||||
if __name__=='__main__':
|
||||
import argparse,os
|
||||
parser = argparse.ArgumentParser(description='nova package creator and editor')
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
sign_parser = subparsers.add_parser('sign',help='sign npk file')
|
||||
sign_parser.add_argument('input',type=str, help='Input file')
|
||||
sign_parser.add_argument('output',type=str,help='Output file')
|
||||
verify_parser = subparsers.add_parser('verify',help='Verify npk file')
|
||||
verify_parser.add_argument('input',type=str, help='Input file')
|
||||
create_option_parser = subparsers.add_parser('create',help='Create option.npk file')
|
||||
create_option_parser.add_argument('npk_file',type=str,help='From npk file')
|
||||
create_option_parser.add_argument('squashfs',type=str,help='option squashfs file')
|
||||
create_option_parser.add_argument('output',type=str,help='Output file')
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.command =='sign':
|
||||
print(f'Signing {args.input}')
|
||||
npk = NovaPackage.load(args.input)
|
||||
kcdsa_private_key = bytes.fromhex(os.environ['CUSTOM_LICENSE_PRIVATE_KEY'])
|
||||
eddsa_private_key = bytes.fromhex(os.environ['CUSTOM_NPK_SIGN_PRIVATE_KEY'])
|
||||
npk.sign(kcdsa_private_key,eddsa_private_key)
|
||||
npk.save(args.output)
|
||||
elif args.command == 'verify':
|
||||
npk = NovaPackage.load(args.input)
|
||||
print(f'Verifying {args.input} ',end="")
|
||||
kcdsa_public_key = bytes.fromhex(os.environ['CUSTOM_LICENSE_PUBLIC_KEY'])
|
||||
eddsa_public_key = bytes.fromhex(os.environ['CUSTOM_NPK_SIGN_PUBLIC_KEY'])
|
||||
if npk.verify(kcdsa_public_key,eddsa_public_key):
|
||||
print('Valid')
|
||||
exit(0)
|
||||
else:
|
||||
print('Invalid')
|
||||
exit(1)
|
||||
elif args.command =='create':
|
||||
print(f'Creating option.npk from {args.npk_file}')
|
||||
kcdsa_private_key = bytes.fromhex(os.environ['CUSTOM_LICENSE_PRIVATE_KEY'])
|
||||
eddsa_private_key = bytes.fromhex(os.environ['CUSTOM_NPK_SIGN_PRIVATE_KEY'])
|
||||
option_npk = NovaPackage.load(args.npk_file)
|
||||
option_npk[NpkPartID.NAME_INFO].data.name = 'option'
|
||||
option_npk[NpkPartID.DESCRIPTION].data = b'option package has busybox ash'
|
||||
option_npk[NpkPartID.NULL_BLOCK].data = b''
|
||||
option_npk[NpkPartID.SQUASHFS].data = open(args.squashfs,'rb').read()
|
||||
option_npk.sign(kcdsa_private_key,eddsa_private_key)
|
||||
option_npk.save(args.output)
|
||||
print(f'Created {args.output}')
|
||||
else:
|
||||
parser.print_help()
|
111
patch.py
Normal file
111
patch.py
Normal file
@ -0,0 +1,111 @@
|
||||
import subprocess,lzma
|
||||
import struct,os
|
||||
from npk import NovaPackage,NpkPartID,NpkFileContainer
|
||||
|
||||
def patch_bzimage(data:bytes,key_dict:dict):
|
||||
print(f'bzImage size : {len(data)}')
|
||||
PE_TEXT_SECTION_OFFSET = 414
|
||||
HEADER_PAYLOAD_OFFSET = 584
|
||||
HEADER_PAYLOAD_LENGTH_OFFSET = HEADER_PAYLOAD_OFFSET + 4
|
||||
text_section_raw_data = struct.unpack_from('<I',data,PE_TEXT_SECTION_OFFSET)[0]
|
||||
payload_offset = text_section_raw_data +struct.unpack_from('<I',data,HEADER_PAYLOAD_OFFSET)[0]
|
||||
payload_length = struct.unpack_from('<I',data,HEADER_PAYLOAD_LENGTH_OFFSET)[0]
|
||||
payload_length = payload_length - 4 #last 4 bytes is uncompressed size(z_output_len)
|
||||
print(f'vmlinux xz offset : {payload_offset}')
|
||||
print(f'vmlinux xz size : {payload_length}')
|
||||
z_output_len = struct.unpack_from('<I',data,payload_offset+payload_length)[0]
|
||||
print(f'z_output_len : {z_output_len}')
|
||||
vmlinux_xz = data[payload_offset:payload_offset+payload_length]
|
||||
vmlinux = lzma.decompress(vmlinux_xz)
|
||||
print(f'vmlinux size : {len(vmlinux)}')
|
||||
assert z_output_len == len(vmlinux), 'vmlinux size is not equal to expected'
|
||||
CPIO_HEADER_MAGIC = b'07070100'
|
||||
CPIO_FOOTER_MAGIC = b'TRAILER!!!\x00\x00\x00\x00' #545241494C455221212100000000
|
||||
cpio_offset1 = vmlinux.index(CPIO_HEADER_MAGIC)
|
||||
initramfs = vmlinux[cpio_offset1:]
|
||||
cpio_offset2 = initramfs.index(CPIO_FOOTER_MAGIC)+len(CPIO_FOOTER_MAGIC)
|
||||
initramfs = initramfs[:cpio_offset2]
|
||||
new_initramfs = initramfs
|
||||
for old_public_key,new_public_key in key_dict.items():
|
||||
if old_public_key in new_initramfs:
|
||||
print(f'initramfs public key patched {old_public_key[:16].hex().upper()}...')
|
||||
new_initramfs = new_initramfs.replace(old_public_key,new_public_key)
|
||||
new_vmlinux = vmlinux.replace(initramfs,new_initramfs)
|
||||
new_vmlinux_xz = lzma.compress(new_vmlinux,check=lzma.CHECK_CRC32,filters=[
|
||||
{"id": lzma.FILTER_X86},
|
||||
{"id": lzma.FILTER_LZMA2, "preset": 8,'dict_size': 32*1024*1024},
|
||||
])
|
||||
new_payload_length = len(new_vmlinux_xz)
|
||||
print(f'new vmlinux xz size : {new_payload_length}')
|
||||
assert new_payload_length <= payload_length , 'new vmlinux.xz size is too big'
|
||||
new_payload_length = new_payload_length + 4 #last 4 bytes is uncompressed size(z_output_len)
|
||||
new_data = bytearray(data)
|
||||
struct.pack_into('<I',new_data,HEADER_PAYLOAD_LENGTH_OFFSET,new_payload_length)
|
||||
vmlinux_xz += struct.pack('<I',z_output_len)
|
||||
new_vmlinux_xz += struct.pack('<I',z_output_len)
|
||||
new_vmlinux_xz = new_vmlinux_xz.ljust(len(vmlinux_xz),b'\0')
|
||||
new_data = new_data.replace(vmlinux_xz,new_vmlinux_xz)
|
||||
print(f'new bzImage size : {len(new_data)}')
|
||||
return new_data
|
||||
|
||||
def run_shell_command(command):
|
||||
process = subprocess.run(command, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
return process.stdout, process.stderr
|
||||
|
||||
def patch_squashfs(path,key_dict):
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
file = os.path.join(root,file)
|
||||
if os.path.isfile(file):
|
||||
data = open(file,'rb').read()
|
||||
for old_public_key,new_public_key in key_dict.items():
|
||||
if old_public_key in data:
|
||||
print(f'{file} public key patched {old_public_key[:16].hex().upper()}...')
|
||||
data = data.replace(old_public_key,new_public_key)
|
||||
open(file,'wb').write(data)
|
||||
|
||||
def patch_system_npk(npk_file,key_dict):
|
||||
npk = NovaPackage.load(npk_file)
|
||||
file_container = NpkFileContainer.unserialize_from(npk[NpkPartID.FILE_CONTAINER].data)
|
||||
for item in file_container:
|
||||
if item.name == b'boot/EFI/BOOT/BOOTX64.EFI':
|
||||
print(f'patch {item.name} ...')
|
||||
item.data = patch_bzimage(item.data,key_dict)
|
||||
open('linux','wb').write(item.data)
|
||||
break
|
||||
npk[NpkPartID.FILE_CONTAINER].data = file_container.serialize()
|
||||
try:
|
||||
squashfs_file = 'squashfs.sfs'
|
||||
extract_dir = 'squashfs-root'
|
||||
open(squashfs_file,'wb').write(npk[NpkPartID.SQUASHFS].data)
|
||||
print(f"extract {squashfs_file} ...")
|
||||
_, stderr = run_shell_command(f"unsquashfs -d {extract_dir} {squashfs_file}")
|
||||
print(stderr.decode())
|
||||
patch_squashfs(extract_dir,key_dict)
|
||||
print(f"pack {extract_dir} ...")
|
||||
run_shell_command(f"rm -f {squashfs_file}")
|
||||
_, stderr = run_shell_command(f"mksquashfs {extract_dir} {squashfs_file} -quiet -comp xz -no-xattrs -b 256k")
|
||||
print(stderr.decode())
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print(f"clean ...")
|
||||
run_shell_command(f"rm -rf {extract_dir}")
|
||||
npk[NpkPartID.SQUASHFS].data = open(squashfs_file,'rb').read()
|
||||
run_shell_command(f"rm -f {squashfs_file}")
|
||||
kcdsa_private_key = bytes.fromhex(os.environ['CUSTOM_LICENSE_PRIVATE_KEY'])
|
||||
eddsa_private_key = bytes.fromhex(os.environ['CUSTOM_NPK_SIGN_PRIVATE_KEY'])
|
||||
npk.sign(kcdsa_private_key,eddsa_private_key)
|
||||
npk.save(npk_file)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os,sys
|
||||
key_dict = {
|
||||
bytes.fromhex(os.environ['MIKRO_LICENSE_PUBLIC_KEY']):bytes.fromhex(os.environ['CUSTOM_LICENSE_PUBLIC_KEY']),
|
||||
bytes.fromhex(os.environ['MIKRO_NPK_SIGN_PUBLIC_LKEY']):bytes.fromhex(os.environ['CUSTOM_NPK_SIGN_PUBLIC_KEY'])
|
||||
}
|
||||
if len(sys.argv) == 2:
|
||||
print(f'patching {sys.argv[1]} ...')
|
||||
patch_system_npk(sys.argv[1],key_dict)
|
||||
else:
|
||||
print('usage: python patch.py npk_file')
|
||||
|
BIN
routeros.png
Normal file
BIN
routeros.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
317
sha256.py
Normal file
317
sha256.py
Normal file
@ -0,0 +1,317 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2012 Dave Pifke.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
"""SHA256 (FIPS 180-3) implementation for experimentation."""
|
||||
|
||||
import binascii
|
||||
import codecs
|
||||
import collections
|
||||
import struct
|
||||
import sys
|
||||
|
||||
if sys.version > '3':
|
||||
long = int
|
||||
|
||||
|
||||
class SHA256(object):
|
||||
"""
|
||||
SHA256 (FIPS 180-3) implementation for experimentation.
|
||||
|
||||
This is an implementation of the hash function designed not for
|
||||
efficiency, but for clarity and ability to experiment. The details
|
||||
of the algorithm are abstracted out with subclassing in mind.
|
||||
|
||||
"""
|
||||
|
||||
# Container for the state registers between rounds:
|
||||
State = collections.namedtuple('State', 'a b c d e f g h')
|
||||
|
||||
# From FIPS 180-3 section 5.3.3 (page 15):
|
||||
INITIAL_STATE = State(
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
|
||||
)
|
||||
|
||||
# From FIPS 180-3 section 4.2.2 (page 11):
|
||||
K = (
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
||||
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
|
||||
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
||||
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
||||
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
|
||||
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
||||
)
|
||||
|
||||
# Abstract bitwise operations, which can be overridden to provide tracing
|
||||
# or alternate implementations:
|
||||
@staticmethod
|
||||
def _sum_mod32(*args):
|
||||
return sum(args) & 0xffffffff
|
||||
@classmethod
|
||||
def _xor(cls, *args):
|
||||
if len(args) == 2:
|
||||
return args[0] ^ args[1]
|
||||
else:
|
||||
return args[0] ^ cls._xor(*args[1:])
|
||||
_and = staticmethod(lambda x, y: x & y)
|
||||
_invert = staticmethod(lambda x: ~x)
|
||||
|
||||
# Operations defined by FIPS 180-3 section 3.2 (page 8):
|
||||
_rrot = staticmethod(lambda x, n: ((x & 0xffffffff) >> n) | (x << (32 - n)) & 0xffffffff)
|
||||
_shr = staticmethod(lambda x, n: (x & 0xffffffff) >> n)
|
||||
|
||||
# Operations defined by FIPS 180-3 section 4.1.2 (page 10):
|
||||
_ch = classmethod(lambda cls, x, y, z: cls._xor(cls._and(x, y), cls._and(cls._invert(x), z)))
|
||||
_maj = classmethod(lambda cls, x, y, z: cls._xor(cls._and(x, y), cls._and(x, z), cls._and(y, z)))
|
||||
_S0 = classmethod(lambda cls, x: cls._xor(cls._rrot(x, 2), cls._rrot(x, 13), cls._rrot(x, 22)))
|
||||
_S1 = classmethod(lambda cls, x: cls._xor(cls._rrot(x, 6), cls._rrot(x, 11), cls._rrot(x, 25)))
|
||||
_s0 = classmethod(lambda cls, x: cls._xor(cls._rrot(x, 7), cls._rrot(x, 18), cls._shr(x, 3)))
|
||||
_s1 = classmethod(lambda cls, x: cls._xor(cls._rrot(x, 17), cls._rrot(x, 19), cls._shr(x, 10)))
|
||||
|
||||
# Operations defined by FIPS 180-3 section 6.2.2 (page 22):
|
||||
_T1 = classmethod(lambda cls, prev, w, k: cls._sum_mod32(cls._S1(prev.e), cls._ch(prev.e, prev.f, prev.g), prev.h, w, k))
|
||||
_T2 = classmethod(lambda cls, prev: cls._sum_mod32(cls._S0(prev.a), cls._maj(prev.a, prev.b, prev.c)))
|
||||
|
||||
@classmethod
|
||||
def _round(cls, number, w, prev=INITIAL_STATE):
|
||||
"""
|
||||
Performs one round of SHA256 message transformation, returning the new
|
||||
message state. See FIPS 180-3 section 6.2.2 step 3 (pages 21-22).
|
||||
|
||||
:param number:
|
||||
The round number.
|
||||
|
||||
:param w:
|
||||
The expanded word of the input for this round.
|
||||
|
||||
:param prev:
|
||||
Named tuple containing the working state from the previous round.
|
||||
|
||||
"""
|
||||
|
||||
t1 = cls._T1(prev, w, cls.K[number % 64])
|
||||
return cls.State(
|
||||
a=cls._sum_mod32(t1, cls._T2(prev)),
|
||||
b=prev.a,
|
||||
c=prev.b,
|
||||
d=prev.c,
|
||||
e=cls._sum_mod32(prev.d, t1),
|
||||
f=prev.e,
|
||||
g=prev.f,
|
||||
h=prev.g
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _finalize(cls, state, initial_state=INITIAL_STATE):
|
||||
"""
|
||||
Returns the intermediate state after the final round for a given block
|
||||
is complete. See FIPS 180-3 section 6.2.2 step 4 (page 22).
|
||||
|
||||
:param state:
|
||||
The digest state after the final round.
|
||||
|
||||
:param initial_state:
|
||||
The digest state from before the first round.
|
||||
|
||||
"""
|
||||
|
||||
return cls.State(
|
||||
a=cls._sum_mod32(state.a, initial_state.a),
|
||||
b=cls._sum_mod32(state.b, initial_state.b),
|
||||
c=cls._sum_mod32(state.c, initial_state.c),
|
||||
d=cls._sum_mod32(state.d, initial_state.d),
|
||||
e=cls._sum_mod32(state.e, initial_state.e),
|
||||
f=cls._sum_mod32(state.f, initial_state.f),
|
||||
g=cls._sum_mod32(state.g, initial_state.g),
|
||||
h=cls._sum_mod32(state.h, initial_state.h)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _expand_message(cls, message):
|
||||
"""
|
||||
Returns a list of 64 32-bit words based upon 16 32-bit words from the
|
||||
message block being hashed. See FIPS 180-3 section 6.2.2 step 1
|
||||
(page 21).
|
||||
|
||||
:param message:
|
||||
Array of 16 32-bit values (512 bits total).
|
||||
|
||||
"""
|
||||
|
||||
assert len(message) == 16, '_expand_message() got %d words, expected 16' % len(message)
|
||||
|
||||
w = list(message)
|
||||
for i in range(16, 64):
|
||||
w.append(cls._sum_mod32(w[i - 16], cls._s0(w[i - 15]), w[i - 7], cls._s1(w[i - 2])))
|
||||
|
||||
return w
|
||||
|
||||
@classmethod
|
||||
def _process_block(cls, message, state=INITIAL_STATE, round_offset=0):
|
||||
"""
|
||||
Processes a block of message data, returning the new digest state
|
||||
(the intermediate hash value). See FIPS 180-3 section 6.2.2 (pages
|
||||
21 and 22).
|
||||
|
||||
:param message:
|
||||
Byte string of length 64 containing the block data to hash.
|
||||
|
||||
:param state:
|
||||
The digest state from the previous block.
|
||||
|
||||
:param round_offset:
|
||||
The _round() method can be overridden to report intermediate hash
|
||||
values, in which case it's useful to know how many rounds came
|
||||
before. This argument allows the caller to specify as much.
|
||||
|
||||
"""
|
||||
|
||||
assert len(message) == 64, '_process_block() got %d bytes, expected 64' % len(message)
|
||||
assert not round_offset % 64, 'round_offset should be a multiple of 64'
|
||||
|
||||
w = cls._expand_message(struct.unpack('>LLLLLLLLLLLLLLLL', message))
|
||||
|
||||
midstate = state
|
||||
for i in range(64):
|
||||
midstate = cls._round(round_offset + i, w[i], midstate)
|
||||
|
||||
return cls._finalize(midstate, state)
|
||||
|
||||
@classmethod
|
||||
def _pad_message(cls, message, length):
|
||||
"""
|
||||
Returns a list containing the final 1 or 2 message blocks, which
|
||||
include the message padding per FIPS 180-3 section 5.1.1 (page 13).
|
||||
|
||||
:param message:
|
||||
Byte string containing the final block data to hash. Should be
|
||||
less than a full block's worth (63 bytes or less).
|
||||
|
||||
:param length:
|
||||
Length of the message, in bits.
|
||||
|
||||
"""
|
||||
|
||||
assert len(message) < 64, 'Input to _pad_message() must be less than 512 bits'
|
||||
|
||||
if len(message) <= 55:
|
||||
# Append trailing 1 bit, then padding, then length
|
||||
return [b''.join((
|
||||
message,
|
||||
b'\x80',
|
||||
b'\x00' * (55 - len(message)),
|
||||
struct.pack('>LL', length >> 32, length & 0xffffffff),
|
||||
))]
|
||||
|
||||
else:
|
||||
# Not enough room to append length, return two blocks:
|
||||
return [
|
||||
# First is trailing 1 bit, then padding
|
||||
b''.join((
|
||||
message,
|
||||
b'\x80',
|
||||
b'\x00' * (63 - len(message)),
|
||||
)),
|
||||
# Next is more padding, then length
|
||||
b''.join((
|
||||
b'\x00' * 56,
|
||||
struct.pack('>LL', length >> 32, length & 0xffffffff),
|
||||
)),
|
||||
]
|
||||
|
||||
def __init__(self, message=b'', round_offset=0):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param message:
|
||||
Initial data to pass to update().
|
||||
|
||||
:param round_offset:
|
||||
The _round() method can be overridden to report intermediate hash
|
||||
values, in which case it's useful to know how many rounds came
|
||||
before. For applications that perform double-hashing, you can
|
||||
specify the number of rounds from the previous hash instance
|
||||
using this parameter.
|
||||
|
||||
"""
|
||||
|
||||
self.state = self.INITIAL_STATE
|
||||
self.length = long(0)
|
||||
self.buffer = b''
|
||||
self.round_offset = round_offset
|
||||
|
||||
self.update(message)
|
||||
|
||||
def update(self, message):
|
||||
"""
|
||||
Updates the hash with the contents of *message*.
|
||||
|
||||
Hashing uses 512-bit blocks, so the message is buffered until there's
|
||||
enough data to process a complete block. When digest() is called,
|
||||
any remaining data in the buffer will be padded and digested.
|
||||
|
||||
:param message:
|
||||
A byte string to digest.
|
||||
|
||||
"""
|
||||
|
||||
message = bytes(message)
|
||||
self.length += len(message) * 8
|
||||
self.buffer = b''.join((self.buffer, message))
|
||||
|
||||
while len(self.buffer) >= 64:
|
||||
self.state = self._process_block(self.buffer[:64], self.state, self.round_offset)
|
||||
self.buffer = self.buffer[64:]
|
||||
self.round_offset += 64
|
||||
|
||||
def digest(self):
|
||||
"""
|
||||
Returns the SHA256 digest of the message.
|
||||
|
||||
The hash is based on all data passed thus far via the constructor and
|
||||
update(). Any buffered data will be processed (along with the
|
||||
terminating length), however the internal state is not modified. This
|
||||
means that update() can safely be used again after digest().
|
||||
|
||||
"""
|
||||
|
||||
final_state = self.state
|
||||
for block in self._pad_message(self.buffer, self.length):
|
||||
final_state = self._process_block(block, final_state, self.round_offset)
|
||||
|
||||
return struct.pack('>LLLLLLLL', *final_state)
|
||||
|
||||
def hexdigest(self):
|
||||
"""Like digest(), but returns a hexadecimal string."""
|
||||
|
||||
return binascii.hexlify(self.digest())
|
188
toyecc/ASN1.py
Normal file
188
toyecc/ASN1.py
Normal file
@ -0,0 +1,188 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
try:
|
||||
from pyasn1.type import univ, namedtype, tag
|
||||
import pyasn1.codec.ber.decoder
|
||||
|
||||
class ECPVer(univ.Integer):
|
||||
"""RFC 3279: Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
|
||||
|
||||
ECPVer ::= INTEGER {ecpVer1(1)}
|
||||
"""
|
||||
pass
|
||||
|
||||
class FieldElement(univ.OctetString):
|
||||
"""RFC 3279: Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
|
||||
|
||||
FieldElement ::= OCTET STRING
|
||||
"""
|
||||
pass
|
||||
|
||||
class ECPoint(univ.OctetString):
|
||||
"""RFC 3279: Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
|
||||
|
||||
ECPoint ::= OCTET STRING
|
||||
"""
|
||||
pass
|
||||
|
||||
class Curve(univ.Sequence):
|
||||
"""RFC 3279: Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
|
||||
|
||||
Curve ::= SEQUENCE {
|
||||
a FieldElement,
|
||||
b FieldElement,
|
||||
seed BIT STRING OPTIONAL
|
||||
}
|
||||
"""
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType("a", FieldElement()),
|
||||
namedtype.NamedType("b", FieldElement()),
|
||||
namedtype.OptionalNamedType("seed", univ.BitString()),
|
||||
)
|
||||
|
||||
class FieldID(univ.Sequence):
|
||||
"""RFC 3279: Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
|
||||
|
||||
FieldID ::= SEQUENCE {
|
||||
fieldType OBJECT IDENTIFIER,
|
||||
parameters ANY DEFINED BY fieldType
|
||||
}
|
||||
"""
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType("fieldType", univ.ObjectIdentifier()),
|
||||
namedtype.NamedType("parameters", univ.Any()),
|
||||
)
|
||||
|
||||
class SpecifiedECDomain(univ.Sequence):
|
||||
"""RFC 3279: Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
|
||||
|
||||
ECParameters ::= SEQUENCE {
|
||||
version ECPVer, -- version is always 1
|
||||
fieldID FieldID, -- identifies the finite field over which the curve is defined
|
||||
curve Curve, -- coefficients a and b of the elliptic curve
|
||||
base ECPoint, -- specifies the base point P on the elliptic curve
|
||||
order INTEGER, -- the order n of the base point
|
||||
cofactor INTEGER OPTIONAL -- The integer h = #E(Fq)/n
|
||||
}
|
||||
"""
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType("version", ECPVer()),
|
||||
namedtype.NamedType("fieldID", FieldID()),
|
||||
namedtype.NamedType("curve", Curve()),
|
||||
namedtype.NamedType("base", ECPoint()),
|
||||
namedtype.NamedType("order", univ.Integer()),
|
||||
namedtype.OptionalNamedType("cofactor", univ.Integer()),
|
||||
)
|
||||
|
||||
class ECParameters(univ.Choice):
|
||||
"""RFC 5480: Elliptic Curve Cryptography Subject Public Key Information
|
||||
|
||||
ECParameters ::= CHOICE {
|
||||
namedCurve OBJECT IDENTIFIER
|
||||
implicitCurve NULL
|
||||
specifiedCurve SpecifiedECDomain
|
||||
}
|
||||
"""
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType("namedCurve", univ.ObjectIdentifier()),
|
||||
namedtype.NamedType("implicitCurve", univ.Null()),
|
||||
namedtype.NamedType("specifiedCurve", SpecifiedECDomain()),
|
||||
)
|
||||
|
||||
|
||||
class ECPrivateKey(univ.Sequence):
|
||||
"""RFC 5915: Elliptic Curve Private Key Structure
|
||||
|
||||
ECPrivateKey ::= SEQUENCE {
|
||||
version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
|
||||
privateKey OCTET STRING,
|
||||
parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
|
||||
publicKey [1] BIT STRING OPTIONAL
|
||||
}
|
||||
"""
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType("version", univ.Integer()),
|
||||
namedtype.NamedType("privateKey", univ.OctetString()),
|
||||
namedtype.OptionalNamedType("parameters", ECParameters().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
|
||||
namedtype.OptionalNamedType("publicKey", univ.BitString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))),
|
||||
)
|
||||
|
||||
class AlgorithmIdentifier(univ.Sequence):
|
||||
"""RFC 5480: Elliptic Curve Cryptography Subject Public Key Information
|
||||
|
||||
AlgorithmIdentifier ::= SEQUENCE {
|
||||
algorithm OBJECT IDENTIFIER,
|
||||
parameters ANY DEFINED BY algorithm OPTIONAL
|
||||
}
|
||||
"""
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType("algorithm", univ.ObjectIdentifier()),
|
||||
namedtype.NamedType("parameters", ECParameters()),
|
||||
)
|
||||
|
||||
class ECPublicKey(univ.Sequence):
|
||||
"""RFC 5480: Elliptic Curve Cryptography Subject Public Key Information
|
||||
|
||||
SubjectPublicKeyInfo ::= SEQUENCE {
|
||||
algorithm AlgorithmIdentifier,
|
||||
subjectPublicKey BIT STRING
|
||||
}
|
||||
"""
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType("algorithm", AlgorithmIdentifier()),
|
||||
namedtype.NamedType("subjectPublicKey", univ.BitString()),
|
||||
)
|
||||
|
||||
class FieldFPParameters(univ.Integer):
|
||||
"""For F_P fields, the field parameters is just the integer P."""
|
||||
pass
|
||||
|
||||
__have_asn1 = True
|
||||
except ImportError:
|
||||
__have_asn1 = False
|
||||
|
||||
def have_asn1_support():
|
||||
return __have_asn1
|
||||
|
||||
def __assert_asn1_support():
|
||||
if not have_asn1_support():
|
||||
raise Exception("ASN.1 support is required, but the pyasn1 library could not be imported. Functionality not available.")
|
||||
|
||||
def parse_asn1_field_params_fp(derdata):
|
||||
"""Parse an ASN.1 DER encoded field parameter for fields in F_P."""
|
||||
__assert_asn1_support()
|
||||
(parsed, tail) = pyasn1.codec.ber.decoder.decode(derdata, asn1Spec = FieldFPParameters())
|
||||
return parsed
|
||||
|
||||
def parse_asn1_public_key(derdata):
|
||||
"""Parse an ASN.1 DER encoded EC public key."""
|
||||
__assert_asn1_support()
|
||||
(parsed, tail) = pyasn1.codec.ber.decoder.decode(derdata, asn1Spec = ECPublicKey())
|
||||
return parsed
|
||||
|
||||
def parse_asn1_private_key(derdata):
|
||||
"""Parse an ASN.1 DER encoded EC private key."""
|
||||
__assert_asn1_support()
|
||||
(parsed, tail) = pyasn1.codec.ber.decoder.decode(derdata, asn1Spec = ECPrivateKey())
|
||||
return parsed
|
132
toyecc/AffineCurvePoint.py
Normal file
132
toyecc/AffineCurvePoint.py
Normal file
@ -0,0 +1,132 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
import math
|
||||
|
||||
from .FieldElement import FieldElement
|
||||
from .PointOps import PointOpEDDSAEncoding, PointOpCurveConversion, PointOpNaiveOrderCalculation, PointOpSerialization, PointOpScalarMultiplicationXOnly
|
||||
|
||||
class AffineCurvePoint(PointOpEDDSAEncoding, PointOpCurveConversion, PointOpNaiveOrderCalculation, PointOpSerialization, PointOpScalarMultiplicationXOnly):
|
||||
"""Represents a point on a curve in affine (x, y) representation."""
|
||||
|
||||
def __init__(self, x, y, curve):
|
||||
"""Generate a curve point (x, y) on the curve 'curve'. x and y have to
|
||||
be integers. If the neutral element of the group O (for some curves,
|
||||
this is a point at infinity) should be created, use the static method
|
||||
'neutral', since representations of O differ on various curves (e.g. in
|
||||
short Weierstrass curves, they have no explicit notation in affine
|
||||
space while on twisted Edwards curves they do."""
|
||||
# Either x and y are None (Point at Infty) or both are defined
|
||||
assert(((x is None) and (y is None)) or ((x is not None) and (y is not None)))
|
||||
assert((x is None) or isinstance(x, int))
|
||||
assert((y is None) or isinstance(y, int))
|
||||
if x is None:
|
||||
# Point at infinity
|
||||
self._x = None
|
||||
self._y = None
|
||||
else:
|
||||
self._x = FieldElement(x, curve.p)
|
||||
self._y = FieldElement(y, curve.p)
|
||||
self._curve = curve
|
||||
|
||||
@staticmethod
|
||||
def neutral(curve):
|
||||
"""Returns the neutral element of the curve group."""
|
||||
return curve.neutral()
|
||||
|
||||
@property
|
||||
def is_neutral(self):
|
||||
"""Indicates if the point is the neutral element O of the curve (point
|
||||
at infinity for some curves)."""
|
||||
return self.curve.is_neutral(self)
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
"""Affine X component of the point, field element of p."""
|
||||
return self._x
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
"""Affine Y component of the point, field element of p."""
|
||||
return self._y
|
||||
|
||||
@property
|
||||
def curve(self):
|
||||
"""Curve that the point is located on."""
|
||||
return self._curve
|
||||
|
||||
def __add__(self, other):
|
||||
"""Returns the point addition."""
|
||||
assert(isinstance(other, AffineCurvePoint))
|
||||
return self.curve.point_addition(self, other)
|
||||
|
||||
def __rmul__(self, other):
|
||||
return self * other
|
||||
|
||||
def __neg__(self):
|
||||
"""Returns the conjugated point."""
|
||||
return self.curve.point_conjugate(self)
|
||||
|
||||
def __mul__(self, scalar):
|
||||
"""Returns the scalar point multiplication. The scalar needs to be an
|
||||
integer value."""
|
||||
assert(isinstance(scalar, int))
|
||||
assert(scalar >= 0)
|
||||
|
||||
result = self.curve.neutral()
|
||||
n = self
|
||||
if scalar > 0:
|
||||
for bit in range(scalar.bit_length()):
|
||||
if (scalar & (1 << bit)):
|
||||
result = result + n
|
||||
n = n + n
|
||||
#assert(result.oncurve())
|
||||
return result
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.x, self.y) == (other.x, other.y)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.x, self.y))
|
||||
|
||||
def oncurve(self):
|
||||
"""Indicates if the given point is satisfying the curve equation (i.e.
|
||||
if it is a point on the curve)."""
|
||||
return self.curve.oncurve(self)
|
||||
|
||||
def compress(self):
|
||||
"""Returns the compressed point format (if this is possible on the
|
||||
given curve)."""
|
||||
return self.curve.compress(self)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def __str__(self):
|
||||
if self.is_neutral:
|
||||
return "(neutral)"
|
||||
else:
|
||||
return "(0x%x, 0x%x)" % (int(self.x), int(self.y))
|
59
toyecc/CRT.py
Normal file
59
toyecc/CRT.py
Normal file
@ -0,0 +1,59 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
from .FieldElement import FieldElement
|
||||
|
||||
class CRT():
|
||||
"""Implements the Chinese Remainder Theorem algorithm where a number of
|
||||
modular congruences are given that all need to be satisfied."""
|
||||
def __init__(self):
|
||||
self._moduli = { }
|
||||
|
||||
def add(self, value, modulus):
|
||||
"""Adds a value that shall be returned when the result is taken modulo
|
||||
the given modulus."""
|
||||
assert(modulus not in self._moduli)
|
||||
assert(isinstance(value, int))
|
||||
assert(isinstance(modulus, int))
|
||||
self._moduli[modulus] = value
|
||||
return self
|
||||
|
||||
def solve(self):
|
||||
"""Solve the Chinese Remainder Theorem for the given values and
|
||||
moduli."""
|
||||
# Calculate product of all moduli
|
||||
product = 1
|
||||
for modulus in self._moduli.keys():
|
||||
product *= modulus
|
||||
|
||||
# Then determine the solution
|
||||
solution = 0
|
||||
for modulus in self._moduli.keys():
|
||||
if self._moduli[modulus] == 0:
|
||||
continue
|
||||
|
||||
rem_product = product // modulus
|
||||
one_value = int(FieldElement(rem_product, modulus).inverse())
|
||||
solution += rem_product * one_value * self._moduli[modulus]
|
||||
|
||||
return solution % product
|
842
toyecc/CurveDB.py
Normal file
842
toyecc/CurveDB.py
Normal file
@ -0,0 +1,842 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
import enum
|
||||
import collections
|
||||
from .ShortWeierstrassCurve import ShortWeierstrassCurve
|
||||
from .MontgomeryCurve import MontgomeryCurve
|
||||
from .TwistedEdwardsCurve import TwistedEdwardsCurve
|
||||
from .Singleton import singleton
|
||||
from .FieldElement import FieldElement
|
||||
from .Exceptions import DuplicateCurveException, NoSuchCurveException, UnsupportedFieldException
|
||||
from .ASN1 import parse_asn1_field_params_fp
|
||||
from .AffineCurvePoint import AffineCurvePoint
|
||||
from .CurveQuirks import CurveQuirkEdDSASetPrivateKeyMSB, CurveQuirkEdDSAEnsurePrimeOrderSubgroup, CurveQuirkSigningHashFunction
|
||||
from . import Tools
|
||||
|
||||
@singleton
|
||||
class CurveDB(object):
|
||||
def __init__(self):
|
||||
self._entries = { }
|
||||
self._primary_names = set()
|
||||
self._taken_names = set()
|
||||
|
||||
def _checknames(self, curvenames):
|
||||
if len(curvenames & self._taken_names) > 0:
|
||||
taken_names = ", ".join(sorted(list(curvenames & self._taken_names)))
|
||||
raise DuplicateCurveException("Curve(s) named %s already registered in curve DB." % (taken_names))
|
||||
|
||||
def register(self, entry):
|
||||
"""Registers a curve in the curve database."""
|
||||
all_names = set(name.lower() for name in entry.all_aliases)
|
||||
self._checknames(all_names)
|
||||
self._taken_names |= all_names
|
||||
self._primary_names.add(entry.name)
|
||||
|
||||
self._entries[entry.primary_name.lower()] = entry
|
||||
for aliasname in entry.aliases:
|
||||
clone = entry.clone(secondary_name = aliasname)
|
||||
self._entries[aliasname.lower()] = clone
|
||||
|
||||
def curvenames(self):
|
||||
"""Returns the primary names of all curves in the DB."""
|
||||
return (curve.name for curve in self._entries.values() if (curve.is_aka is False))
|
||||
|
||||
def allcurvenames(self):
|
||||
"""Returns all names of all curves in the DB. This includes duplicate
|
||||
AKAs such as secp224r1 which is also known as wap-wsg-idm-ecid-wtls12
|
||||
albeit under a different OID."""
|
||||
return (curve.name for curve in self._entries.values())
|
||||
|
||||
def find_duplicate_curves(self):
|
||||
"""Returns curves in which the domain parameters (including the
|
||||
coordinates of the generator point G) are identical. This can happen if
|
||||
identical curves are registered under the same name."""
|
||||
params = collections.defaultdict(list)
|
||||
for curve in self:
|
||||
params[tuple(sorted(curve.domain_params))].append(curve.name)
|
||||
return [ curves for (param, curves) in params.items() if (len(curves) > 1) ]
|
||||
|
||||
def getentry(self, name):
|
||||
"""Returns a specific curve entry by its case-insensitive name."""
|
||||
name = name.lower()
|
||||
if name not in self._entries:
|
||||
raise KeyError("Curve named '%s' is not known in curve database." % (name))
|
||||
return self._entries[name]
|
||||
|
||||
def get_curve_from_asn1(self, asn1):
|
||||
"""This function will take a parsed ASN.1 ECParameters class as input
|
||||
and try to return the curve specified within. If the ECParameters
|
||||
specify a named curve by its's OID then a lookup is performed against
|
||||
the curve database and that named curve returned on success if
|
||||
non-ambiguous. If the parameters are exclicitly stated, then an unnamed
|
||||
ShortWeierstrassCurve is constructed."""
|
||||
|
||||
if asn1["namedCurve"] is not None:
|
||||
# Curve is encoded as OID, look up from curve DB
|
||||
curve_oid = str(asn1["namedCurve"])
|
||||
entries = [ entry for entry in self if (entry.oid == curve_oid) ]
|
||||
if len(entries) == 0:
|
||||
raise NoSuchCurveException("Trying to load curve with OID %s from curve DB, but no such curve is present in database." % (curve_oid))
|
||||
elif len(entries) > 1:
|
||||
raise Exception("Trying to load curve with OID %s from curve DB, but found %d curves (refuse to guess in the face of ambiguity)." % (curve_oid, len(entries)))
|
||||
curve = entries[0]()
|
||||
elif asn1["specifiedCurve"] is not None:
|
||||
field_type_oid = str(asn1["specifiedCurve"]["fieldID"]["fieldType"])
|
||||
if field_type_oid == "1.2.840.10045.1.1":
|
||||
# F_P curve is encoded in explicit form
|
||||
p = int(parse_asn1_field_params_fp(asn1["specifiedCurve"]["fieldID"]["parameters"]))
|
||||
a = Tools.bytestoint(asn1["specifiedCurve"]["curve"]["a"])
|
||||
b = Tools.bytestoint(asn1["specifiedCurve"]["curve"]["b"])
|
||||
G = bytes(asn1["specifiedCurve"]["base"])
|
||||
(Gx, Gy) = AffineCurvePoint.deserialize_uncompressed(G)
|
||||
n = int(asn1["specifiedCurve"]["order"])
|
||||
h = int(asn1["specifiedCurve"]["cofactor"])
|
||||
curve = ShortWeierstrassCurve(p = p, a = a, b = b, n = n, h = h, Gx = Gx, Gy = Gy)
|
||||
else:
|
||||
# Maybe F_2^N curve or some other, unsupported type
|
||||
raise UnsupportedFieldException("Only supports elliptic curves in F_P are supported, but the requested field type OID was %s." % (field_type_oid))
|
||||
else:
|
||||
raise NoSuchCurveException("Cannot load implicit curve.")
|
||||
return curve
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterates over the curve DB entries."""
|
||||
for name in self.curvenames():
|
||||
yield self._entries[name.lower()]
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""Returns a curve (not a curve DB entry) by its name."""
|
||||
return self.getentry(name)()
|
||||
|
||||
def __str__(self):
|
||||
return "CurveDB<%d unique curves, %d total>" % (len(self._primary_names), len(self._entries))
|
||||
|
||||
|
||||
class _CurveDBEntry(object):
|
||||
def __init__(self, primary_name, curve_class, domain_params, **kwargs):
|
||||
allowed_kwargs = set(("oid", "alt_oids", "aliases", "origin", "secure", "quirks"))
|
||||
wrong_args = kwargs.keys() - allowed_kwargs
|
||||
if len(wrong_args) > 0:
|
||||
raise Exception("Illegal keyword arguments: %s" % (", ".join(sorted(wrong_args))))
|
||||
|
||||
assert(primary_name is not None)
|
||||
self._primary_name = primary_name
|
||||
self._secondary_name = None
|
||||
self._curve_class = curve_class
|
||||
self._domain_params = domain_params
|
||||
self._oid = kwargs.get("oid")
|
||||
self._alt_oids = kwargs.get("alt_oids")
|
||||
self._aliases = kwargs.get("aliases")
|
||||
self._origin = kwargs.get("origin")
|
||||
self._secure = kwargs.get("secure", True)
|
||||
self._quirks = kwargs.get("quirks", [ ])
|
||||
self._instance = None
|
||||
|
||||
def clone(self, secondary_name = None):
|
||||
clone = _CurveDBEntry(primary_name = self._primary_name, curve_class = self._curve_class, domain_params = self._domain_params, oid = self._oid, alt_oids = self._alt_oids, aliases = self._aliases, origin = self._origin, secure = self._secure)
|
||||
clone._instance = self._instance
|
||||
clone._secondary_name = secondary_name
|
||||
return clone
|
||||
|
||||
@property
|
||||
def is_aka(self):
|
||||
"""Returns if this curve entry is an AKA ('also known as') for a
|
||||
different curve (but maybe with a different OID). Example: prime192v1
|
||||
and secp192r1 refer to identical curves, but 'prime192v1' is the
|
||||
internally considered primary name while 'secp192r1' is considered to
|
||||
be an AKA."""
|
||||
return self._secondary_name is not None
|
||||
|
||||
@property
|
||||
def primary_name(self):
|
||||
return self._primary_name
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if self._secondary_name is not None:
|
||||
return self._secondary_name
|
||||
else:
|
||||
return self._primary_name
|
||||
|
||||
@property
|
||||
def fieldsize_bits(self):
|
||||
return self._domain_params["p"].bit_length()
|
||||
|
||||
@property
|
||||
def secure(self):
|
||||
return self._secure
|
||||
|
||||
@property
|
||||
def origin(self):
|
||||
return self._origin
|
||||
|
||||
@property
|
||||
def bits_security_estimate(self):
|
||||
if not self.secure:
|
||||
return 0
|
||||
else:
|
||||
# Require instanciation of the class
|
||||
self()
|
||||
return self._instance.security_bit_estimate
|
||||
|
||||
def get_alternative_oid(self, name):
|
||||
"""Returns the alternative OID if it has one."""
|
||||
if self._alt_oids is not None:
|
||||
return self._alt_oids.get(name)
|
||||
|
||||
@property
|
||||
def oid(self):
|
||||
if (self._alt_oids is not None) and (self.name in self._alt_oids):
|
||||
return self._alt_oids[self.name]
|
||||
else:
|
||||
return self._oid
|
||||
|
||||
@property
|
||||
def aliases(self):
|
||||
if self._aliases is not None:
|
||||
yield from self._aliases
|
||||
|
||||
@property
|
||||
def all_aliases(self):
|
||||
yield self._primary_name
|
||||
yield from self.aliases
|
||||
|
||||
@property
|
||||
def prettyname(self):
|
||||
if self._instance is None:
|
||||
return self._curve_class.pretty_name
|
||||
else:
|
||||
return self._instance.prettyname
|
||||
|
||||
@property
|
||||
def domain_params(self):
|
||||
if self._instance is None:
|
||||
return dict(self._domain_params)
|
||||
else:
|
||||
return self._instance.domainparamdict
|
||||
|
||||
@property
|
||||
def prettytitle(self):
|
||||
return "%d bit %s Curve" % (self.fieldsize_bits, self.prettyname)
|
||||
|
||||
def dump(self, domain = False):
|
||||
print("%s: %s" % (self.name, self.prettytitle))
|
||||
if self._aliases is not None:
|
||||
print("Aliases: %s" % (", ".join(sorted(list(self._aliases)))))
|
||||
if self._oid is not None:
|
||||
print("OID : %s" % (self._oid))
|
||||
if domain:
|
||||
print("Domain parameters:")
|
||||
for (key, value) in sorted(self.domain_params.items()):
|
||||
if isinstance(value, FieldElement):
|
||||
value = value.sigint()
|
||||
print(" %-10s %s" % (key, value))
|
||||
|
||||
def __call__(self):
|
||||
"""Instanciate the curve."""
|
||||
if self._instance is None:
|
||||
# Instanciate actual curve
|
||||
params = self._domain_params
|
||||
params["name"] = self.name
|
||||
params["quirks"] = self._quirks
|
||||
self._instance = self._curve_class(**params)
|
||||
return self._instance
|
||||
|
||||
def __str__(self):
|
||||
if self._secondary_name is not None:
|
||||
return "CurveDBEntry<%s AKA %s>" % (self.primary_name, self._secondary_name)
|
||||
else:
|
||||
return "CurveDBEntry<%s>" % (self.name)
|
||||
|
||||
cdb = CurveDB()
|
||||
cdb.register(_CurveDBEntry("brainpoolP160r1", ShortWeierstrassCurve, {
|
||||
"a": 0x340e7be2a280eb74e2be61bada745d97e8f7c300,
|
||||
"b": 0x1e589a8595423412134faa2dbdec95c8d8675e58,
|
||||
"p": 0xe95e4a5f737059dc60dfc7ad95b3d8139515620f,
|
||||
"n": 0xe95e4a5f737059dc60df5991d45029409e60fc09,
|
||||
"h": 1,
|
||||
"Gx": 0xbed5af16ea3f6a4f62938c4631eb5af7bdbcdbc3,
|
||||
"Gy": 0x1667cb477a1a8ec338f94741669c976316da6321,
|
||||
}, oid = "1.3.36.3.3.2.8.1.1.1", origin = "ECC Brainpool"))
|
||||
|
||||
cdb.register(_CurveDBEntry("brainpoolP160t1", ShortWeierstrassCurve, {
|
||||
"a": 0xe95e4a5f737059dc60dfc7ad95b3d8139515620c,
|
||||
"b": 0x7a556b6dae535b7b51ed2c4d7daa7a0b5c55f380,
|
||||
"p": 0xe95e4a5f737059dc60dfc7ad95b3d8139515620f,
|
||||
"n": 0xe95e4a5f737059dc60df5991d45029409e60fc09,
|
||||
"h": 1,
|
||||
"Gx": 0xb199b13b9b34efc1397e64baeb05acc265ff2378,
|
||||
"Gy": 0xadd6718b7c7c1961f0991b842443772152c9e0ad,
|
||||
}, oid = "1.3.36.3.3.2.8.1.1.2", origin = "ECC Brainpool"))
|
||||
|
||||
cdb.register(_CurveDBEntry("brainpoolP192r1", ShortWeierstrassCurve, {
|
||||
"a": 0x6a91174076b1e0e19c39c031fe8685c1cae040e5c69a28ef,
|
||||
"b": 0x469a28ef7c28cca3dc721d044f4496bcca7ef4146fbf25c9,
|
||||
"p": 0xc302f41d932a36cda7a3463093d18db78fce476de1a86297,
|
||||
"n": 0xc302f41d932a36cda7a3462f9e9e916b5be8f1029ac4acc1,
|
||||
"h": 1,
|
||||
"Gx": 0xc0a0647eaab6a48753b033c56cb0f0900a2f5c4853375fd6,
|
||||
"Gy": 0x14b690866abd5bb88b5f4828c1490002e6773fa2fa299b8f,
|
||||
}, oid = "1.3.36.3.3.2.8.1.1.3", origin = "ECC Brainpool"))
|
||||
|
||||
cdb.register(_CurveDBEntry("brainpoolP192t1", ShortWeierstrassCurve, {
|
||||
"a": 0xc302f41d932a36cda7a3463093d18db78fce476de1a86294,
|
||||
"b": 0x13d56ffaec78681e68f9deb43b35bec2fb68542e27897b79,
|
||||
"p": 0xc302f41d932a36cda7a3463093d18db78fce476de1a86297,
|
||||
"n": 0xc302f41d932a36cda7a3462f9e9e916b5be8f1029ac4acc1,
|
||||
"h": 1,
|
||||
"Gx": 0x3ae9e58c82f63c30282e1fe7bbf43fa72c446af6f4618129,
|
||||
"Gy": 0x97e2c5667c2223a902ab5ca449d0084b7e5b3de7ccc01c9,
|
||||
}, oid = "1.3.36.3.3.2.8.1.1.4", origin = "ECC Brainpool"))
|
||||
|
||||
cdb.register(_CurveDBEntry("brainpoolP224r1", ShortWeierstrassCurve, {
|
||||
"a": 0x68a5e62ca9ce6c1c299803a6c1530b514e182ad8b0042a59cad29f43,
|
||||
"b": 0x2580f63ccfe44138870713b1a92369e33e2135d266dbb372386c400b,
|
||||
"p": 0xd7c134aa264366862a18302575d1d787b09f075797da89f57ec8c0ff,
|
||||
"n": 0xd7c134aa264366862a18302575d0fb98d116bc4b6ddebca3a5a7939f,
|
||||
"h": 1,
|
||||
"Gx": 0xd9029ad2c7e5cf4340823b2a87dc68c9e4ce3174c1e6efdee12c07d,
|
||||
"Gy": 0x58aa56f772c0726f24c6b89e4ecdac24354b9e99caa3f6d3761402cd,
|
||||
}, oid = "1.3.36.3.3.2.8.1.1.5", origin = "ECC Brainpool"))
|
||||
|
||||
cdb.register(_CurveDBEntry("brainpoolP224t1", ShortWeierstrassCurve, {
|
||||
"a": 0xd7c134aa264366862a18302575d1d787b09f075797da89f57ec8c0fc,
|
||||
"b": 0x4b337d934104cd7bef271bf60ced1ed20da14c08b3bb64f18a60888d,
|
||||
"p": 0xd7c134aa264366862a18302575d1d787b09f075797da89f57ec8c0ff,
|
||||
"n": 0xd7c134aa264366862a18302575d0fb98d116bc4b6ddebca3a5a7939f,
|
||||
"h": 1,
|
||||
"Gx": 0x6ab1e344ce25ff3896424e7ffe14762ecb49f8928ac0c76029b4d580,
|
||||
"Gy": 0x374e9f5143e568cd23f3f4d7c0d4b1e41c8cc0d1c6abd5f1a46db4c,
|
||||
}, oid = "1.3.36.3.3.2.8.1.1.6", origin = "ECC Brainpool"))
|
||||
|
||||
cdb.register(_CurveDBEntry("brainpoolP256r1", ShortWeierstrassCurve, {
|
||||
"a": 0x7d5a0975fc2c3057eef67530417affe7fb8055c126dc5c6ce94a4b44f330b5d9,
|
||||
"b": 0x26dc5c6ce94a4b44f330b5d9bbd77cbf958416295cf7e1ce6bccdc18ff8c07b6,
|
||||
"p": 0xa9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377,
|
||||
"n": 0xa9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a7,
|
||||
"h": 1,
|
||||
"Gx": 0x8bd2aeb9cb7e57cb2c4b482ffc81b7afb9de27e1e3bd23c23a4453bd9ace3262,
|
||||
"Gy": 0x547ef835c3dac4fd97f8461a14611dc9c27745132ded8e545c1d54c72f046997,
|
||||
}, oid = "1.3.36.3.3.2.8.1.1.7", origin = "ECC Brainpool"))
|
||||
|
||||
cdb.register(_CurveDBEntry("brainpoolP256t1", ShortWeierstrassCurve, {
|
||||
"a": 0xa9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5374,
|
||||
"b": 0x662c61c430d84ea4fe66a7733d0b76b7bf93ebc4af2f49256ae58101fee92b04,
|
||||
"p": 0xa9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377,
|
||||
"n": 0xa9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a7,
|
||||
"h": 1,
|
||||
"Gx": 0xa3e8eb3cc1cfe7b7732213b23a656149afa142c47aafbc2b79a191562e1305f4,
|
||||
"Gy": 0x2d996c823439c56d7f7b22e14644417e69bcb6de39d027001dabe8f35b25c9be,
|
||||
}, oid = "1.3.36.3.3.2.8.1.1.8", origin = "ECC Brainpool"))
|
||||
|
||||
cdb.register(_CurveDBEntry("brainpoolP320r1", ShortWeierstrassCurve, {
|
||||
"a": 0x3ee30b568fbab0f883ccebd46d3f3bb8a2a73513f5eb79da66190eb085ffa9f492f375a97d860eb4,
|
||||
"b": 0x520883949dfdbc42d3ad198640688a6fe13f41349554b49acc31dccd884539816f5eb4ac8fb1f1a6,
|
||||
"p": 0xd35e472036bc4fb7e13c785ed201e065f98fcfa6f6f40def4f92b9ec7893ec28fcd412b1f1b32e27,
|
||||
"n": 0xd35e472036bc4fb7e13c785ed201e065f98fcfa5b68f12a32d482ec7ee8658e98691555b44c59311,
|
||||
"h": 1,
|
||||
"Gx": 0x43bd7e9afb53d8b85289bcc48ee5bfe6f20137d10a087eb6e7871e2a10a599c710af8d0d39e20611,
|
||||
"Gy": 0x14fdd05545ec1cc8ab4093247f77275e0743ffed117182eaa9c77877aaac6ac7d35245d1692e8ee1,
|
||||
}, oid = "1.3.36.3.3.2.8.1.1.9", origin = "ECC Brainpool"))
|
||||
|
||||
cdb.register(_CurveDBEntry("brainpoolP320t1", ShortWeierstrassCurve, {
|
||||
"a": 0xd35e472036bc4fb7e13c785ed201e065f98fcfa6f6f40def4f92b9ec7893ec28fcd412b1f1b32e24,
|
||||
"b": 0xa7f561e038eb1ed560b3d147db782013064c19f27ed27c6780aaf77fb8a547ceb5b4fef422340353,
|
||||
"p": 0xd35e472036bc4fb7e13c785ed201e065f98fcfa6f6f40def4f92b9ec7893ec28fcd412b1f1b32e27,
|
||||
"n": 0xd35e472036bc4fb7e13c785ed201e065f98fcfa5b68f12a32d482ec7ee8658e98691555b44c59311,
|
||||
"h": 1,
|
||||
"Gx": 0x925be9fb01afc6fb4d3e7d4990010f813408ab106c4f09cb7ee07868cc136fff3357f624a21bed52,
|
||||
"Gy": 0x63ba3a7a27483ebf6671dbef7abb30ebee084e58a0b077ad42a5a0989d1ee71b1b9bc0455fb0d2c3,
|
||||
}, oid = "1.3.36.3.3.2.8.1.1.10", origin = "ECC Brainpool"))
|
||||
|
||||
cdb.register(_CurveDBEntry("brainpoolP384r1", ShortWeierstrassCurve, {
|
||||
"a": 0x7bc382c63d8c150c3c72080ace05afa0c2bea28e4fb22787139165efba91f90f8aa5814a503ad4eb04a8c7dd22ce2826,
|
||||
"b": 0x4a8c7dd22ce28268b39b55416f0447c2fb77de107dcd2a62e880ea53eeb62d57cb4390295dbc9943ab78696fa504c11,
|
||||
"p": 0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec53,
|
||||
"n": 0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202e9046565,
|
||||
"h": 1,
|
||||
"Gx": 0x1d1c64f068cf45ffa2a63a81b7c13f6b8847a3e77ef14fe3db7fcafe0cbd10e8e826e03436d646aaef87b2e247d4af1e,
|
||||
"Gy": 0x8abe1d7520f9c2a45cb1eb8e95cfd55262b70b29feec5864e19c054ff99129280e4646217791811142820341263c5315,
|
||||
}, oid = "1.3.36.3.3.2.8.1.1.11", origin = "ECC Brainpool"))
|
||||
|
||||
cdb.register(_CurveDBEntry("brainpoolP384t1", ShortWeierstrassCurve, {
|
||||
"a": 0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec50,
|
||||
"b": 0x7f519eada7bda81bd826dba647910f8c4b9346ed8ccdc64e4b1abd11756dce1d2074aa263b88805ced70355a33b471ee,
|
||||
"p": 0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec53,
|
||||
"n": 0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202e9046565,
|
||||
"h": 1,
|
||||
"Gx": 0x18de98b02db9a306f2afcd7235f72a819b80ab12ebd653172476fecd462aabffc4ff191b946a5f54d8d0aa2f418808cc,
|
||||
"Gy": 0x25ab056962d30651a114afd2755ad336747f93475b7a1fca3b88f2b6a208ccfe469408584dc2b2912675bf5b9e582928,
|
||||
}, oid = "1.3.36.3.3.2.8.1.1.12", origin = "ECC Brainpool"))
|
||||
|
||||
cdb.register(_CurveDBEntry("brainpoolP512r1", ShortWeierstrassCurve, {
|
||||
"a": 0x7830a3318b603b89e2327145ac234cc594cbdd8d3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c98b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94ca,
|
||||
"b": 0x3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c98b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94cadc083e67984050b75ebae5dd2809bd638016f723,
|
||||
"p": 0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717d4d9b009bc66842aecda12ae6a380e62881ff2f2d82c68528aa6056583a48f3,
|
||||
"n": 0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca70330870553e5c414ca92619418661197fac10471db1d381085ddaddb58796829ca90069,
|
||||
"h": 1,
|
||||
"Gx": 0x81aee4bdd82ed9645a21322e9c4c6a9385ed9f70b5d916c1b43b62eef4d0098eff3b1f78e2d0d48d50d1687b93b97d5f7c6d5047406a5e688b352209bcb9f822,
|
||||
"Gy": 0x7dde385d566332ecc0eabfa9cf7822fdf209f70024a57b1aa000c55b881f8111b2dcde494a5f485e5bca4bd88a2763aed1ca2b2fa8f0540678cd1e0f3ad80892,
|
||||
}, oid = "1.3.36.3.3.2.8.1.1.13", origin = "ECC Brainpool"))
|
||||
|
||||
cdb.register(_CurveDBEntry("brainpoolP512t1", ShortWeierstrassCurve, {
|
||||
"a": 0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717d4d9b009bc66842aecda12ae6a380e62881ff2f2d82c68528aa6056583a48f0,
|
||||
"b": 0x7cbbbcf9441cfab76e1890e46884eae321f70c0bcb4981527897504bec3e36a62bcdfa2304976540f6450085f2dae145c22553b465763689180ea2571867423e,
|
||||
"p": 0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717d4d9b009bc66842aecda12ae6a380e62881ff2f2d82c68528aa6056583a48f3,
|
||||
"n": 0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca70330870553e5c414ca92619418661197fac10471db1d381085ddaddb58796829ca90069,
|
||||
"h": 1,
|
||||
"Gx": 0x640ece5c12788717b9c1ba06cbc2a6feba85842458c56dde9db1758d39c0313d82ba51735cdb3ea499aa77a7d6943a64f7a3f25fe26f06b51baa2696fa9035da,
|
||||
"Gy": 0x5b534bd595f5af0fa2c892376c84ace1bb4e3019b71634c01131159cae03cee9d9932184beef216bd71df2dadf86a627306ecff96dbb8bace198b61e00f8b332,
|
||||
}, oid = "1.3.36.3.3.2.8.1.1.14", origin = "ECC Brainpool"))
|
||||
|
||||
cdb.register(_CurveDBEntry("prime192v1", ShortWeierstrassCurve, {
|
||||
"a": 0xfffffffffffffffffffffffffffffffefffffffffffffffc,
|
||||
"b": 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1,
|
||||
"p": 0xfffffffffffffffffffffffffffffffeffffffffffffffff,
|
||||
"n": 0xffffffffffffffffffffffff99def836146bc9b1b4d22831,
|
||||
"h": 1,
|
||||
"Gx": 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012,
|
||||
"Gy": 0x7192b95ffc8da78631011ed6b24cdd573f977a11e794811,
|
||||
}, aliases = [ "secp192r1", "NIST P-192", "ansip192r1" ], oid = "1.2.840.10045.3.1.1", origin = "Certicom Standards for Efficient Cryptography (SEC) 2 / ANSI X9.62 / FIPS 186-2 / NIST Recommended Elliptic Curves for Federal Government Use"))
|
||||
|
||||
cdb.register(_CurveDBEntry("prime192v2", ShortWeierstrassCurve, {
|
||||
"a": 0xfffffffffffffffffffffffffffffffefffffffffffffffc,
|
||||
"b": 0xcc22d6dfb95c6b25e49c0d6364a4e5980c393aa21668d953,
|
||||
"p": 0xfffffffffffffffffffffffffffffffeffffffffffffffff,
|
||||
"n": 0xfffffffffffffffffffffffe5fb1a724dc80418648d8dd31,
|
||||
"h": 1,
|
||||
"Gx": 0xeea2bae7e1497842f2de7769cfe9c989c072ad696f48034a,
|
||||
"Gy": 0x6574d11d69b6ec7a672bb82a083df2f2b0847de970b2de15,
|
||||
}, oid = "1.2.840.10045.3.1.2", origin = "ANSI X9.62"))
|
||||
|
||||
cdb.register(_CurveDBEntry("prime192v3", ShortWeierstrassCurve, {
|
||||
"a": 0xfffffffffffffffffffffffffffffffefffffffffffffffc,
|
||||
"b": 0x22123dc2395a05caa7423daeccc94760a7d462256bd56916,
|
||||
"p": 0xfffffffffffffffffffffffffffffffeffffffffffffffff,
|
||||
"n": 0xffffffffffffffffffffffff7a62d031c83f4294f640ec13,
|
||||
"h": 1,
|
||||
"Gx": 0x7d29778100c65a1da1783716588dce2b8b4aee8e228f1896,
|
||||
"Gy": 0x38a90f22637337334b49dcb66a6dc8f9978aca7648a943b0,
|
||||
}, oid = "1.2.840.10045.3.1.3", origin = "ANSI X9.62"))
|
||||
|
||||
cdb.register(_CurveDBEntry("prime239v1", ShortWeierstrassCurve, {
|
||||
"a": 0x7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc,
|
||||
"b": 0x6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a,
|
||||
"p": 0x7fffffffffffffffffffffff7fffffffffff8000000000007fffffffffff,
|
||||
"n": 0x7fffffffffffffffffffffff7fffff9e5e9a9f5d9071fbd1522688909d0b,
|
||||
"h": 1,
|
||||
"Gx": 0xffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf,
|
||||
"Gy": 0x7debe8e4e90a5dae6e4054ca530ba04654b36818ce226b39fccb7b02f1ae,
|
||||
}, oid = "1.2.840.10045.3.1.4", origin = "ANSI X9.62"))
|
||||
|
||||
cdb.register(_CurveDBEntry("prime239v2", ShortWeierstrassCurve, {
|
||||
"a": 0x7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc,
|
||||
"b": 0x617fab6832576cbbfed50d99f0249c3fee58b94ba0038c7ae84c8c832f2c,
|
||||
"p": 0x7fffffffffffffffffffffff7fffffffffff8000000000007fffffffffff,
|
||||
"n": 0x7fffffffffffffffffffffff800000cfa7e8594377d414c03821bc582063,
|
||||
"h": 1,
|
||||
"Gx": 0x38af09d98727705120c921bb5e9e26296a3cdcf2f35757a0eafd87b830e7,
|
||||
"Gy": 0x5b0125e4dbea0ec7206da0fc01d9b081329fb555de6ef460237dff8be4ba,
|
||||
}, oid = "1.2.840.10045.3.1.5", origin = "ANSI X9.62"))
|
||||
|
||||
cdb.register(_CurveDBEntry("prime239v3", ShortWeierstrassCurve, {
|
||||
"a": 0x7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc,
|
||||
"b": 0x255705fa2a306654b1f4cb03d6a750a30c250102d4988717d9ba15ab6d3e,
|
||||
"p": 0x7fffffffffffffffffffffff7fffffffffff8000000000007fffffffffff,
|
||||
"n": 0x7fffffffffffffffffffffff7fffff975deb41b3a6057c3c432146526551,
|
||||
"h": 1,
|
||||
"Gx": 0x6768ae8e18bb92cfcf005c949aa2c6d94853d0e660bbf854b1c9505fe95a,
|
||||
"Gy": 0x1607e6898f390c06bc1d552bad226f3b6fcfe48b6e818499af18e3ed6cf3,
|
||||
}, oid = "1.2.840.10045.3.1.6", origin = "ANSI X9.62"))
|
||||
|
||||
cdb.register(_CurveDBEntry("prime256v1", ShortWeierstrassCurve, {
|
||||
"a": 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc,
|
||||
"b": 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b,
|
||||
"p": 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff,
|
||||
"n": 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551,
|
||||
"h": 1,
|
||||
"Gx": 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
|
||||
"Gy": 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5,
|
||||
}, aliases = [ "secp256r1", "NIST P-256" ], oid = "1.2.840.10045.3.1.7", origin = "Certicom Standards for Efficient Cryptography (SEC) 2 / ANSI X9.62 / FIPS 186-2 / NIST Recommended Elliptic Curves for Federal Government Use"))
|
||||
|
||||
cdb.register(_CurveDBEntry("secp112r1", ShortWeierstrassCurve, {
|
||||
"a": 0xdb7c2abf62e35e668076bead2088,
|
||||
"b": 0x659ef8ba043916eede8911702b22,
|
||||
"p": 0xdb7c2abf62e35e668076bead208b,
|
||||
"n": 0xdb7c2abf62e35e7628dfac6561c5,
|
||||
"h": 1,
|
||||
"Gx": 0x9487239995a5ee76b55f9c2f098,
|
||||
"Gy": 0xa89ce5af8724c0a23e0e0ff77500,
|
||||
}, aliases = [ "wap-wsg-idm-ecid-wtls6" ], oid = "1.3.132.0.6", alt_oids = { "wap-wsg-idm-ecid-wtls6": "2.23.43.1.4.6" }, origin = "Certicom Standards for Efficient Cryptography (SEC) 2 / Wireless Application Protocol WAP-261-WTLS-20010406a"))
|
||||
|
||||
cdb.register(_CurveDBEntry("secp112r2", ShortWeierstrassCurve, {
|
||||
"a": 0x6127c24c05f38a0aaaf65c0ef02c,
|
||||
"b": 0x51def1815db5ed74fcc34c85d709,
|
||||
"p": 0xdb7c2abf62e35e668076bead208b,
|
||||
"n": 0x36df0aafd8b8d7597ca10520d04b,
|
||||
"h": 4,
|
||||
"Gx": 0x4ba30ab5e892b4e1649dd0928643,
|
||||
"Gy": 0xadcd46f5882e3747def36e956e97,
|
||||
}, oid = "1.3.132.0.7", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
|
||||
|
||||
cdb.register(_CurveDBEntry("secp128r1", ShortWeierstrassCurve, {
|
||||
"a": 0xfffffffdfffffffffffffffffffffffc,
|
||||
"b": 0xe87579c11079f43dd824993c2cee5ed3,
|
||||
"p": 0xfffffffdffffffffffffffffffffffff,
|
||||
"n": 0xfffffffe0000000075a30d1b9038a115,
|
||||
"h": 1,
|
||||
"Gx": 0x161ff7528b899b2d0c28607ca52c5b86,
|
||||
"Gy": 0xcf5ac8395bafeb13c02da292dded7a83,
|
||||
}, oid = "1.3.132.0.28", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
|
||||
|
||||
cdb.register(_CurveDBEntry("secp128r2", ShortWeierstrassCurve, {
|
||||
"a": 0xd6031998d1b3bbfebf59cc9bbff9aee1,
|
||||
"b": 0x5eeefca380d02919dc2c6558bb6d8a5d,
|
||||
"p": 0xfffffffdffffffffffffffffffffffff,
|
||||
"n": 0x3fffffff7fffffffbe0024720613b5a3,
|
||||
"h": 4,
|
||||
"Gx": 0x7b6aa5d85e572983e6fb32a7cdebc140,
|
||||
"Gy": 0x27b6916a894d3aee7106fe805fc34b44,
|
||||
}, oid = "1.3.132.0.29", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
|
||||
|
||||
cdb.register(_CurveDBEntry("secp160k1", ShortWeierstrassCurve, {
|
||||
"a": 0,
|
||||
"b": 7,
|
||||
"p": 0x0fffffffffffffffffffffffffffffffeffffac73,
|
||||
"n": 0x100000000000000000001b8fa16dfab9aca16b6b3,
|
||||
"h": 1,
|
||||
"Gx": 0x03b4c382ce37aa192a4019e763036f4f5dd4d7ebb,
|
||||
"Gy": 0x0938cf935318fdced6bc28286531733c3f03c4fee,
|
||||
}, aliases = [ "ansip160k1" ], oid = "1.3.132.0.9", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
|
||||
|
||||
cdb.register(_CurveDBEntry("secp160r1", ShortWeierstrassCurve, {
|
||||
"a": 0x0ffffffffffffffffffffffffffffffff7ffffffc,
|
||||
"b": 0x01c97befc54bd7a8b65acf89f81d4d4adc565fa45,
|
||||
"p": 0x0ffffffffffffffffffffffffffffffff7fffffff,
|
||||
"n": 0x100000000000000000001f4c8f927aed3ca752257,
|
||||
"h": 1,
|
||||
"Gx": 0x04a96b5688ef573284664698968c38bb913cbfc82,
|
||||
"Gy": 0x023a628553168947d59dcc912042351377ac5fb32,
|
||||
}, aliases = [ "ansip160r1" ], oid = "1.3.132.0.8", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
|
||||
|
||||
cdb.register(_CurveDBEntry("secp160r2", ShortWeierstrassCurve, {
|
||||
"a": 0x0fffffffffffffffffffffffffffffffeffffac70,
|
||||
"b": 0x0b4e134d3fb59eb8bab57274904664d5af50388ba,
|
||||
"p": 0x0fffffffffffffffffffffffffffffffeffffac73,
|
||||
"n": 0x100000000000000000000351ee786a818f3a1a16b,
|
||||
"h": 1,
|
||||
"Gx": 0x052dcb034293a117e1f4ff11b30f7199d3144ce6d,
|
||||
"Gy": 0x0feaffef2e331f296e071fa0df9982cfea7d43f2e,
|
||||
}, aliases = [ "ansip160r2", "wap-wsg-idm-ecid-wtls7" ], oid = "1.3.132.0.30", alt_oids = { "wap-wsg-idm-ecid-wtls7": "2.23.43.1.4.7" }, origin = "Certicom Standards for Efficient Cryptography (SEC) 2 / Wireless Application Protocol WAP-261-WTLS-20010406a"))
|
||||
|
||||
cdb.register(_CurveDBEntry("secp192k1", ShortWeierstrassCurve, {
|
||||
"a": 0,
|
||||
"b": 3,
|
||||
"p": 0xfffffffffffffffffffffffffffffffffffffffeffffee37,
|
||||
"n": 0xfffffffffffffffffffffffe26f2fc170f69466a74defd8d,
|
||||
"h": 1,
|
||||
"Gx": 0xdb4ff10ec057e9ae26b07d0280b7f4341da5d1b1eae06c7d,
|
||||
"Gy": 0x9b2f2f6d9c5628a7844163d015be86344082aa88d95e2f9d,
|
||||
}, aliases = [ "ansip192k1" ], oid = "1.3.132.0.31", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
|
||||
|
||||
cdb.register(_CurveDBEntry("secp224k1", ShortWeierstrassCurve, {
|
||||
"a": 0,
|
||||
"b": 5,
|
||||
"p": 0x0fffffffffffffffffffffffffffffffffffffffffffffffeffffe56d,
|
||||
"n": 0x10000000000000000000000000001dce8d2ec6184caf0a971769fb1f7,
|
||||
"h": 1,
|
||||
"Gx": 0x0a1455b334df099df30fc28a169a467e9e47075a90f7e650eb6b7a45c,
|
||||
"Gy": 0x07e089fed7fba344282cafbd6f7e319f7c0b0bd59e2ca4bdb556d61a5,
|
||||
}, aliases = [ "ansip224k1" ], oid = "1.3.132.0.32", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
|
||||
|
||||
cdb.register(_CurveDBEntry("secp224r1", ShortWeierstrassCurve, {
|
||||
"a": 0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe,
|
||||
"b": 0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4,
|
||||
"p": 0xffffffffffffffffffffffffffffffff000000000000000000000001,
|
||||
"n": 0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d,
|
||||
"h": 1,
|
||||
"Gx": 0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21,
|
||||
"Gy": 0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34,
|
||||
}, aliases = [ "ansip224r1", "NIST P-224", "wap-wsg-idm-ecid-wtls12" ], oid = "1.3.132.0.33", alt_oids = { "wap-wsg-idm-ecid-wtls12": "2.23.43.1.4.12" }, origin = "Certicom Standards for Efficient Cryptography (SEC) 2 / FIPS 186-2 / NIST Recommended Elliptic Curves for Federal Government Use / Wireless Application Protocol WAP-261-WTLS-20010406a"))
|
||||
|
||||
cdb.register(_CurveDBEntry("secp256k1", ShortWeierstrassCurve, {
|
||||
"a": 0,
|
||||
"b": 7,
|
||||
"p": 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,
|
||||
"n": 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141,
|
||||
"h": 1,
|
||||
"Gx": 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
|
||||
"Gy": 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8,
|
||||
}, aliases = [ "ansip256k1" ], oid = "1.3.132.0.10", origin = "Certicom Standards for Efficient Cryptography (SEC) 2"))
|
||||
|
||||
cdb.register(_CurveDBEntry("secp384r1", ShortWeierstrassCurve, {
|
||||
"a": 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc,
|
||||
"b": 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef,
|
||||
"p": 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff,
|
||||
"n": 0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973,
|
||||
"h": 1,
|
||||
"Gx": 0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7,
|
||||
"Gy": 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f,
|
||||
}, aliases = [ "ansip384r1", "NIST P-384" ], oid = "1.3.132.0.34", origin = "Certicom Standards for Efficient Cryptography (SEC) 2 / FIPS 186-2 / NIST Recommended Elliptic Curves for Federal Government Use"))
|
||||
|
||||
cdb.register(_CurveDBEntry("secp521r1", ShortWeierstrassCurve, {
|
||||
"a": 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc,
|
||||
"b": 0x051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00,
|
||||
"p": 0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
|
||||
"n": 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409,
|
||||
"h": 1,
|
||||
"Gx": 0x0c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66,
|
||||
"Gy": 0x11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650,
|
||||
}, aliases = [ "NIST P-521", "ansip521r1" ], oid = "1.3.132.0.35", origin = "Certicom Standards for Efficient Cryptography (SEC) 2 / FIPS 186-2 / NIST Recommended Elliptic Curves for Federal Government Use"))
|
||||
|
||||
cdb.register(_CurveDBEntry("wap-wsg-idm-ecid-wtls8", ShortWeierstrassCurve, {
|
||||
"a": 0,
|
||||
"b": 3,
|
||||
"p": 0x0fffffffffffffffffffffffffde7,
|
||||
"n": 0x100000000000001ecea551ad837e9,
|
||||
"h": 1,
|
||||
"Gx": 1,
|
||||
"Gy": 2,
|
||||
}, oid = "2.23.43.1.4.8", origin = "Wireless Application Protocol WAP-261-WTLS-20010406a"))
|
||||
|
||||
cdb.register(_CurveDBEntry("wap-wsg-idm-ecid-wtls9", ShortWeierstrassCurve, {
|
||||
"a": 0,
|
||||
"b": 3,
|
||||
"p": 0x0fffffffffffffffffffffffffffffffffffc808f,
|
||||
"n": 0x100000000000000000001cdc98ae0e2de574abf33,
|
||||
"h": 1,
|
||||
"Gx": 1,
|
||||
"Gy": 2,
|
||||
}, oid = "2.23.43.1.4.9", origin = "Wireless Application Protocol WAP-261-WTLS-20010406a"))
|
||||
|
||||
cdb.register(_CurveDBEntry("Curve25519", MontgomeryCurve, {
|
||||
"a": 486662,
|
||||
"b": 1,
|
||||
"p": (2 ** 255) - 19,
|
||||
"n": (2 ** 252) + 27742317777372353535851937790883648493,
|
||||
"h": 8,
|
||||
"Gx": 0x9,
|
||||
"Gy": 0x5f51e65e475f794b1fe122d388b72eb36dc2b28192839e4dd6163a5d81312c14,
|
||||
}, origin = "2006 Bernstein"))
|
||||
|
||||
# Curve imported from IETF https://tools.ietf.org/html/rfc7748
|
||||
cdb.register(_CurveDBEntry("Curve448", MontgomeryCurve, {
|
||||
"a": 156326,
|
||||
"b": 1,
|
||||
"p": (2 ** 448) - (2 ** 224) - 1,
|
||||
"n": (2 ** 446) - 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d,
|
||||
"h": 4,
|
||||
"Gx": 0x5,
|
||||
"Gy": 0x7d235d1295f5b1f66c98ab6e58326fcecbae5d34f55545d060f75dc28df3f6edb8027e2346430d211312c4b150677af76fd7223d457b5b1a,
|
||||
}, origin = "2006 Bernstein"))
|
||||
|
||||
cdb.register(_CurveDBEntry("Ed25519", TwistedEdwardsCurve, {
|
||||
"a": -1,
|
||||
"d": 37095705934669439343138083508754565189542113879843219016388785533085940283555,
|
||||
"p": (2 ** 255) - 19,
|
||||
"n": (2 ** 252) + 27742317777372353535851937790883648493,
|
||||
"h": 8,
|
||||
"Gx": 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a,
|
||||
"Gy": 0x6666666666666666666666666666666666666666666666666666666666666658,
|
||||
}, origin = "2011 Bernstein-Duif-Lange-Schwabe-Yang", quirks = [ CurveQuirkEdDSASetPrivateKeyMSB(), CurveQuirkEdDSAEnsurePrimeOrderSubgroup(), CurveQuirkSigningHashFunction("sha512") ]))
|
||||
|
||||
# Curve imported from SafeCurves http://safecurves.cr.yp.to
|
||||
cdb.register(_CurveDBEntry("Anomalous", ShortWeierstrassCurve, {
|
||||
"a": 0x98d0fac687d6343eb1a1f595283eb1a1f58d0fac687d635f5e4,
|
||||
"b": 0x4a1f58d0fac687d6343eb1a5e2d6343eb1a1f58d0fac688ab3f,
|
||||
"p": 0xb0000000000000000000000953000000000000000000001f9d7,
|
||||
"n": 0xb0000000000000000000000953000000000000000000001f9d7,
|
||||
"h": 1,
|
||||
"Gx": 0x101efb35fd1963c4871a2d17edaafa7e249807f58f8705126c6,
|
||||
"Gy": 0x22389a3954375834304ba1d509a97de6c07148ea7f5951b20e7,
|
||||
}, secure = False, origin = "Bernstein http://safecurves.cr.yp.to illustration of additive transfer and small discriminant"))
|
||||
|
||||
# Curve imported from SafeCurves http://safecurves.cr.yp.to
|
||||
cdb.register(_CurveDBEntry("M-221", MontgomeryCurve, {
|
||||
"a": 117050,
|
||||
"b": 1,
|
||||
"p": 0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffd,
|
||||
"n": 0x40000000000000000000000000015a08ed730e8a2f77f005042605b,
|
||||
"h": 8,
|
||||
"Gx": 4,
|
||||
"Gy": 0xf7acdd2a4939571d1cef14eca37c228e61dbff10707dc6c08c5056d,
|
||||
}, aliases = [ "Curve2213" ], origin = "2013 Aranha-Barreto-Pereira-Ricardini"))
|
||||
|
||||
# Curve imported from SafeCurves http://safecurves.cr.yp.to
|
||||
cdb.register(_CurveDBEntry("E-222", TwistedEdwardsCurve, {
|
||||
"a": 1,
|
||||
"d": 160102,
|
||||
"p": 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffff8b,
|
||||
"n": 0xffffffffffffffffffffffffffff70cbc95e932f802f31423598cbf,
|
||||
"h": 4,
|
||||
"Gx": 0x19b12bb156a389e55c9768c303316d07c23adab3736eb2bc3eb54e51,
|
||||
"Gy": 28,
|
||||
}, origin = "2013 Aranha-Barreto-Pereira-Ricardini"))
|
||||
|
||||
# Curve imported from SafeCurves http://safecurves.cr.yp.to
|
||||
cdb.register(_CurveDBEntry("Curve1174", TwistedEdwardsCurve, {
|
||||
"a": 1,
|
||||
"d": -1174,
|
||||
"p": 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7,
|
||||
"n": 0x1fffffffffffffffffffffffffffffff77965c4dfd307348944d45fd166c971,
|
||||
"h": 4,
|
||||
"Gx": 0x37fbb0cea308c479343aee7c029a190c021d96a492ecd6516123f27bce29eda,
|
||||
"Gy": 0x6b72f82d47fb7cc6656841169840e0c4fe2dee2af3f976ba4ccb1bf9b46360e,
|
||||
}, origin = "2013 Bernstein-Hamburg-Krasnova-Lange"))
|
||||
|
||||
# Curve imported from SafeCurves http://safecurves.cr.yp.to
|
||||
cdb.register(_CurveDBEntry("BN(2,254)", ShortWeierstrassCurve, {
|
||||
"a": 0,
|
||||
"b": 2,
|
||||
"p": 0x2523648240000001ba344d80000000086121000000000013a700000000000013,
|
||||
"n": 0x2523648240000001ba344d8000000007ff9f800000000010a10000000000000d,
|
||||
"h": 1,
|
||||
"Gx": -1,
|
||||
"Gy": 1,
|
||||
}, origin = "2011 Pereira-Simplicio-Naehrig-Barreto"))
|
||||
|
||||
# Curve imported from SafeCurves http://safecurves.cr.yp.to
|
||||
cdb.register(_CurveDBEntry("ANSSI FRP256v1", ShortWeierstrassCurve, {
|
||||
"a": -3,
|
||||
"b": 0xee353fca5428a9300d4aba754a44c00fdfec0c9ae4b1a1803075ed967b7bb73f,
|
||||
"p": 0xf1fd178c0b3ad58f10126de8ce42435b3961adbcabc8ca6de8fcf353d86e9c03,
|
||||
"n": 0xf1fd178c0b3ad58f10126de8ce42435b53dc67e140d2bf941ffdd459c6d655e1,
|
||||
"h": 1,
|
||||
"Gx": 0xb6b3d4c356c139eb31183d4749d423958c27d2dcaf98b70164c97a2dd98f5cff,
|
||||
"Gy": 0x6142e0f7c8b204911f9271f0f3ecef8c2701c307e8e4c9e183115a1554062cfb,
|
||||
}, oid = "1.2.250.1.223.101.256.1", origin = "Agence nationale de la sécurité des systèmes d'information"))
|
||||
|
||||
# Curve imported from SafeCurves http://safecurves.cr.yp.to
|
||||
cdb.register(_CurveDBEntry("E-382", TwistedEdwardsCurve, {
|
||||
"a": 1,
|
||||
"d": -67254,
|
||||
"p": 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff97,
|
||||
"n": 0xfffffffffffffffffffffffffffffffffffffffffffffffd5fb21f21e95eee17c5e69281b102d2773e27e13fd3c9719,
|
||||
"h": 4,
|
||||
"Gx": 0x196f8dd0eab20391e5f05be96e8d20ae68f840032b0b64352923bab85364841193517dbce8105398ebc0cc9470f79603,
|
||||
"Gy": 17,
|
||||
}, origin = "2013 Aranha-Barreto-Pereira-Ricardini"))
|
||||
|
||||
# Curve imported from SafeCurves http://safecurves.cr.yp.to
|
||||
cdb.register(_CurveDBEntry("M-383", MontgomeryCurve, {
|
||||
"a": 2065150,
|
||||
"b": 1,
|
||||
"p": 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff45,
|
||||
"n": 0x10000000000000000000000000000000000000000000000006c79673ac36ba6e7a32576f7b1b249e46bbc225be9071d7,
|
||||
"h": 8,
|
||||
"Gx": 12,
|
||||
"Gy": 0x1ec7ed04aaf834af310e304b2da0f328e7c165f0e8988abd3992861290f617aa1f1b2e7d0b6e332e969991b62555e77e,
|
||||
}, origin = "2013 Aranha-Barreto-Pereira-Ricardini"))
|
||||
|
||||
# Curve imported from SafeCurves http://safecurves.cr.yp.to
|
||||
cdb.register(_CurveDBEntry("Curve383187", MontgomeryCurve, {
|
||||
"a": 229969,
|
||||
"b": 1,
|
||||
"p": 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff45,
|
||||
"n": 0x1000000000000000000000000000000000000000000000000e85a85287a1488acd41ae84b2b7030446f72088b00a0e21,
|
||||
"h": 8,
|
||||
"Gx": 5,
|
||||
"Gy": 0x1eebe07dc1871896732b12d5504a32370471965c7a11f2c89865f855ab3cbd7c224e3620c31af3370788457dd5ce46df,
|
||||
}, origin = "2013 Aranha-Barreto-Pereira-Ricardini"))
|
||||
|
||||
# Curve imported from SafeCurves http://safecurves.cr.yp.to
|
||||
cdb.register(_CurveDBEntry("Curve41417", TwistedEdwardsCurve, {
|
||||
"a": 1,
|
||||
"d": 3617,
|
||||
"p": 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffef,
|
||||
"n": 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffeb3cc92414cf706022b36f1c0338ad63cf181b0e71a5e106af79,
|
||||
"h": 8,
|
||||
"Gx": 0x1a334905141443300218c0631c326e5fcd46369f44c03ec7f57ff35498a4ab4d6d6ba111301a73faa8537c64c4fd3812f3cbc595,
|
||||
"Gy": 34,
|
||||
}, aliases = [ "Curve3617" ], origin = "2013 Bernstein-Lange"))
|
||||
|
||||
# Curve imported from SafeCurves http://safecurves.cr.yp.to
|
||||
cdb.register(_CurveDBEntry("Ed448-Goldilocks", TwistedEdwardsCurve, {
|
||||
"a": 1,
|
||||
"d": -39081,
|
||||
"p": 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
|
||||
"n": 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3,
|
||||
"h": 4,
|
||||
"Gx": 0x297ea0ea2692ff1b4faff46098453a6a26adf733245f065c3c59d0709cecfa96147eaaf3932d94c63d96c170033f4ba0c7f0de840aed939f,
|
||||
"Gy": 19,
|
||||
}, origin = "2014 Hamburg", quirks = [ CurveQuirkEdDSASetPrivateKeyMSB(), CurveQuirkEdDSAEnsurePrimeOrderSubgroup(), CurveQuirkSigningHashFunction("shake256-114") ]))
|
||||
|
||||
# Curve imported from https://tools.ietf.org/html/rfc8032
|
||||
cdb.register(_CurveDBEntry("Ed448", TwistedEdwardsCurve, {
|
||||
"a": 1,
|
||||
"d": -39081,
|
||||
"p": 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
|
||||
"n": 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3,
|
||||
"h": 4,
|
||||
"Gx": 0x4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e,
|
||||
"Gy": 0x693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14,
|
||||
}, origin = "https://tools.ietf.org/html/rfc8032", quirks = [ CurveQuirkEdDSASetPrivateKeyMSB(), CurveQuirkEdDSAEnsurePrimeOrderSubgroup(), CurveQuirkSigningHashFunction("shake256-114") ]))
|
||||
|
||||
# Curve imported from SafeCurves http://safecurves.cr.yp.to
|
||||
cdb.register(_CurveDBEntry("M-511", MontgomeryCurve, {
|
||||
"a": 530438,
|
||||
"b": 1,
|
||||
"p": 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff45,
|
||||
"n": 0x100000000000000000000000000000000000000000000000000000000000000017b5feff30c7f5677ab2aeebd13779a2ac125042a6aa10bfa54c15bab76baf1b,
|
||||
"h": 8,
|
||||
"Gx": 5,
|
||||
"Gy": 0x2fbdc0ad8530803d28fdbad354bb488d32399ac1cf8f6e01ee3f96389b90c809422b9429e8a43dbf49308ac4455940abe9f1dbca542093a895e30a64af056fa5,
|
||||
}, aliases = [ "Curve511187" ], origin = "2013 Aranha-Barreto-Pereira-Ricardini"))
|
||||
|
||||
# Curve imported from SafeCurves http://safecurves.cr.yp.to
|
||||
cdb.register(_CurveDBEntry("E-521", TwistedEdwardsCurve, {
|
||||
"a": 1,
|
||||
"d": -376014,
|
||||
"p": 0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,
|
||||
"n": 0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd15b6c64746fc85f736b8af5e7ec53f04fbd8c4569a8f1f4540ea2435f5180d6b,
|
||||
"h": 4,
|
||||
"Gx": 0x752cb45c48648b189df90cb2296b2878a3bfd9f42fc6c818ec8bf3c9c0c6203913f6ecc5ccc72434b1ae949d568fc99c6059d0fb13364838aa302a940a2f19ba6c,
|
||||
"Gy": 12,
|
||||
}, origin = "2013 Bernstein-Lange / 2013 Hamburg / 2013 Aranha-Barreto-Pereira-Ricardini"))
|
||||
|
||||
cdb.register(_CurveDBEntry("rigol", ShortWeierstrassCurve, {
|
||||
"a": 0x2982,
|
||||
"b": 0x3408,
|
||||
"p": 0xaebf94cee3e707,
|
||||
"n": 0xaebf94d5c6aa71,
|
||||
"h": 1,
|
||||
"Gx": 0x7a3e808599a525,
|
||||
"Gy": 0x28be7fafd2a052,
|
||||
}, origin = "Rigol DS2xxx feature activation curve"))
|
||||
|
||||
def getcurvedb():
|
||||
"""Returns an instance of the curve database singleton object."""
|
||||
return CurveDB()
|
||||
|
||||
def getcurvenames():
|
||||
"""Returns the names of all curves known to toyecc."""
|
||||
return CurveDB().curvenames()
|
||||
|
||||
def getcurveentry(name):
|
||||
"""Returns a curve entry by its name."""
|
||||
return CurveDB().getentry(name)
|
||||
|
||||
def getcurvebyname(name):
|
||||
"""Returns a curve by its name."""
|
||||
return CurveDB()[name]
|
140
toyecc/CurveOps.py
Normal file
140
toyecc/CurveOps.py
Normal file
@ -0,0 +1,140 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
from .FieldElement import FieldElement
|
||||
from .Exceptions import NoSuchCurveException
|
||||
|
||||
class CurveOpIsomorphism(object):
|
||||
def _twist(self, d = None, sqrt_d = None):
|
||||
"""Returns the twisted curve with the twist coefficient d. If d is a
|
||||
quadratic non-residue mod p then this function will yield a curve that
|
||||
is isomorphous on the field extension GF(sqrt(d)). If it is a quadratic
|
||||
residue, it will return an GF(p)-isomorphous curve."""
|
||||
assert(self.curvetype == "shortweierstrass")
|
||||
ShortWeierstrassCurve = self.__class__
|
||||
|
||||
if sqrt_d is not None:
|
||||
# If a square root is given, then it must be a correct square root
|
||||
assert(sqrt_d ** 2 == d)
|
||||
|
||||
a = self.a * (d ** 2)
|
||||
b = self.b * (d ** 3)
|
||||
if d.is_qr and self.hasgenerator:
|
||||
# Quadratic twist will return an GF(p)-isomorphous curve -> convert
|
||||
# generator point as well
|
||||
if sqrt_d is None:
|
||||
sqrt_d = d.sqrt()[0]
|
||||
Gx = int(self.G.x * d)
|
||||
Gy = int(self.G.y * (sqrt_d ** 3))
|
||||
|
||||
n = self.n
|
||||
h = self.h
|
||||
else:
|
||||
# Quadratic twist will return an isomorphous curve on the
|
||||
# GF(sqrt(d)) field extension -> no generator point conversion for
|
||||
# now
|
||||
# Gx = int(self.G.x * d)
|
||||
# Gy = int(self.G.y * d)
|
||||
Gx = None
|
||||
Gy = None
|
||||
|
||||
# If the original curve had q + 1 - t points, then its twist will
|
||||
# have q + 1 + t points. TODO: Does this help us to find the order
|
||||
# of the generator point G? I don't think it does :-( Leave n and h
|
||||
# therefore unset for the moment.
|
||||
n = None
|
||||
h = None
|
||||
|
||||
return ShortWeierstrassCurve(a = int(a), b = int(b), p = self.p, n = n, h = h, Gx = Gx, Gy = Gy)
|
||||
|
||||
def twist(self, d = None):
|
||||
"""If the twist coefficient d is omitted, the function will
|
||||
automatically look for an arbitrary quadratic non-residue in F_P."""
|
||||
if d == 0:
|
||||
raise Exception("Domain error: d must be nonzero.")
|
||||
elif d is None:
|
||||
# Search for a QNR in F_P
|
||||
d = FieldElement.any_qnr(self.p)
|
||||
else:
|
||||
d = FieldElement(d, self.p)
|
||||
if d.is_qr:
|
||||
raise Exception("Twist requested, but twist coefficient d is a quadratic-residue mod p. Refusing to return a GF(p)-isomorphic curve; if you want this behavior, use twist_fp_isomorphic()")
|
||||
return self._twist(d)
|
||||
|
||||
def twist_fp_isomorphic(self, u):
|
||||
"""Returns a GF(p)-isomorphous curve by applying the substituting
|
||||
transformation x = u^2 x' and y = u^3 y' on the curve equation. The
|
||||
function therefore returns a quadratic twist with d = u^2, i.e. it
|
||||
ensures that the twist coefficient d is a quadratic residue mod p.."""
|
||||
if u == 0:
|
||||
raise Exception("Domain error: u must be nonzero.")
|
||||
return self._twist(FieldElement(u ** 2, self.p), FieldElement(u, self.p))
|
||||
|
||||
def twist_fp_isomorphic_fixed_a(self, a):
|
||||
"""Tries to find an GF(p)-isomorphous curve which has a particular
|
||||
given value for the curve coefficient 'a'."""
|
||||
|
||||
# anew = a * u^4 -> u = quartic_root(anew / a)
|
||||
scalar = a // self.a
|
||||
u = scalar.quartic_root()
|
||||
if u is None:
|
||||
raise NoSuchCurveException("Cannot find an isomorphism so that a = %d because %s has no quartic root in F_P" % (a, scalar))
|
||||
return self.twist_fp_isomorphic(int(u))
|
||||
|
||||
def is_isomorphous_curve(self, other):
|
||||
"""Returns if the given curve 'other' is isomorphous in the same field
|
||||
as the given curve curve."""
|
||||
if other.p != self.p:
|
||||
return False
|
||||
|
||||
try:
|
||||
iso = self.twist_fp_isomorphic_fixed_a(other.a)
|
||||
except NoSuchCurveException:
|
||||
# No isomorphous curve with this value for a exists
|
||||
return False
|
||||
|
||||
# The curves should be identical after the transformation if they're
|
||||
# isomorphous to each other
|
||||
return (iso.a == other.a) and (iso.b == other.b)
|
||||
|
||||
class CurveOpExportSage(object):
|
||||
def export_sage(self, varname = "curve"):
|
||||
"""Exports the elliptic curve to statements that can be used within the
|
||||
SAGE computer algebra system."""
|
||||
|
||||
# EllipticCurve([a1,a2,a3,a4,a6]) means in Sage:
|
||||
# y² + a1 x y + a3 y = x³ + a2 x² + a4 x + a6
|
||||
# i.e. for Short Weierstrass a4 = A, a6 = B
|
||||
|
||||
statements = [ ]
|
||||
statements.append("# %s" % (str(self)))
|
||||
statements.append("%s_p = 0x%x" % (varname, int(self.p)))
|
||||
statements.append("%s_F = GF(%s_p)" % (varname, varname))
|
||||
if self.curvetype == "shortweierstrass":
|
||||
statements.append("%s_a = 0x%x" % (varname, int(self.a)))
|
||||
statements.append("%s_b = 0x%x" % (varname, int(self.b)))
|
||||
statements.append("%s = EllipticCurve(%s_F, [ %s_a, %s_b ])" % (varname, varname, varname, varname))
|
||||
else:
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
return statements
|
77
toyecc/CurveQuirks.py
Normal file
77
toyecc/CurveQuirks.py
Normal file
@ -0,0 +1,77 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
import hashlib
|
||||
|
||||
class CurveQuirk(object):
|
||||
identifier = None
|
||||
|
||||
@property
|
||||
def identity(self):
|
||||
return (self.identifier, )
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.identity == other.identity
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.identity < other.identity
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.identity)
|
||||
|
||||
def __str__(self):
|
||||
return self.identifier
|
||||
|
||||
class CurveQuirkEdDSASetPrivateKeyMSB(CurveQuirk):
|
||||
"""Set the highest significant bit of the private key during EdDSA
|
||||
signature generation. For example, for EdDSA signatures on Ed25519, this
|
||||
would bitwise or the value 'a' with 2^254."""
|
||||
identifier = "EdDSA_set_private_key_MSB"
|
||||
|
||||
class CurveQuirkEdDSAEnsurePrimeOrderSubgroup(CurveQuirk):
|
||||
"""Ensures during EdDSA signature generation that the private key is on a
|
||||
prime-order subgroup. This is done by clearing the amount of bits that is
|
||||
required by the cofactor of the curve (which has to be a power of two for
|
||||
this quirk to work, otherwise it'll fail at runtime). Concretely, for EdDSA
|
||||
on Ed25519 this means that the least significant three bits would be set to
|
||||
zero because the curve cofactor is 8."""
|
||||
identifier = "EdDSA_use_prime_order_subgroup"
|
||||
|
||||
class CurveQuirkSigningHashFunction(CurveQuirk):
|
||||
"""For some curves, the signing hash function is implicitly given. In
|
||||
particular for the Ed448 and Ed25519 variants, this is true. Encode these
|
||||
as a curve quirk."""
|
||||
identifier = "signing_hash_function"
|
||||
|
||||
def __init__(self, sig_fnc_name):
|
||||
self._sig_fnc_name = sig_fnc_name
|
||||
|
||||
def hashdata(self, data):
|
||||
hash_fnc = {
|
||||
"sha512": lambda x: hashlib.sha512(data).digest(),
|
||||
"shake256-114": lambda x: hashlib.shake_256(data).digest(114),
|
||||
}
|
||||
return hash_fnc[self._sig_fnc_name](data)
|
67
toyecc/DivisionPolynomial.py
Normal file
67
toyecc/DivisionPolynomial.py
Normal file
@ -0,0 +1,67 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
from .Polynomial import Polynomial
|
||||
|
||||
class DivisionPolynomial(object):
|
||||
def __init__(self, curve):
|
||||
"""Creates a division polynomial generator which returns \psi_i for the
|
||||
given curve in Weierstrass form."""
|
||||
self._curve = curve
|
||||
assert(self._curve.curvetype == "shortweierstrass")
|
||||
self._cache = { }
|
||||
self._curvepoly = None
|
||||
self._initcache()
|
||||
|
||||
def _initcache(self):
|
||||
(a, b) = (self.curve.a, self.curve.b)
|
||||
x = Polynomial(self.curve.p)
|
||||
self._cache[0] = Polynomial(self.curve.p, 0)
|
||||
self._cache[1] = Polynomial(self.curve.p, 1)
|
||||
self._cache[2] = Polynomial(self.curve.p, 2)
|
||||
self._cache[3] = (3 * x**4) + (6 * a * x**2) + (12 * b * x) - (a**2)
|
||||
self._cache[4] = 4 * (x**6 + (5 * a * x**4) + (20 * b * x**3) - (5 * a**2 * x**2) - (4 * a * b * x) - (8 * b**2) - (a**3))
|
||||
self._curvepoly = x**3 + (a * x) + b
|
||||
|
||||
@property
|
||||
def curve(self):
|
||||
return self._curve
|
||||
|
||||
def __getitem__(self, index):
|
||||
if index not in self._cache:
|
||||
m = index // 2
|
||||
if (index % 2) == 1:
|
||||
# The paper says this would be correct:
|
||||
# result = (self[m + 2] * self[m]**3) - (self[m - 1] * self[m + 1] ** 3)
|
||||
# But MIRACL does it differently. Use the MIRACL approach:
|
||||
if (m % 2) == 0:
|
||||
result = (self._curvepoly**2 * self[m + 2] * self[m]**3) - (self[m - 1] * self[m + 1]**3)
|
||||
else:
|
||||
result = (self[m + 2] * self[m]**3) - (self._curvepoly**2 * self[m - 1] * self[m + 1]**3)
|
||||
else:
|
||||
result = (self[m] // 2) * ((self[m + 2] * self[m - 1]**2) - (self[m - 2] * self[m + 1]**2))
|
||||
self._cache[index] = result
|
||||
return self._cache[index]
|
||||
|
||||
def __str__(self):
|
||||
return "DivisionPolys<%s, %d cached>" % (str(self.curve), len(self._cache))
|
41
toyecc/DocInherit.py
Normal file
41
toyecc/DocInherit.py
Normal file
@ -0,0 +1,41 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2016 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
def doc_inherit(superclass):
|
||||
"""Inherit the docstring of a parent method. The super class needs to be
|
||||
given as the decorator's parameter. Does not overwrite the docstring if
|
||||
there is already one present."""
|
||||
def decorator(decoree):
|
||||
method_name = decoree.__name__
|
||||
if decoree.__doc__ is None:
|
||||
parent_method = getattr(superclass, method_name, None)
|
||||
if parent_method is None:
|
||||
raise Exception("Tried to inherit docstring of method '%s' from class '%s', but the latter does not offer a method by that name." % (method_name, str(superclass)))
|
||||
docstr = parent_method.__doc__
|
||||
if docstr is None:
|
||||
raise Exception("Tried to inherit docstring of method '%s' from class '%s', but that method also does not have a docstring." % (method_name, str(superclass)))
|
||||
decoree.__doc__ = docstr
|
||||
else:
|
||||
raise Exception("Tried to overwrite an already present docstring of %s" % (str(decoree)))
|
||||
return decoree
|
||||
return decorator
|
79
toyecc/ECPrivateKey.py
Normal file
79
toyecc/ECPrivateKey.py
Normal file
@ -0,0 +1,79 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
from .PrivKeyOps import PrivKeyOpECDSASign, PrivKeyOpECIESDecrypt, PrivKeyOpEDDSASign, PrivKeyOpEDDSAKeyGen, PrivKeyOpEDDSAEncode, PrivKeyOpECDH, PrivKeyOpLoad
|
||||
from .ECPublicKey import ECPublicKey
|
||||
from .Random import secure_rand_int_between
|
||||
|
||||
class ECPrivateKey(PrivKeyOpECDSASign, PrivKeyOpECIESDecrypt, PrivKeyOpEDDSASign, PrivKeyOpEDDSAKeyGen, PrivKeyOpEDDSAEncode, PrivKeyOpECDH, PrivKeyOpLoad):
|
||||
"""Represents an elliptic curve private key."""
|
||||
|
||||
def __init__(self, scalar, curve):
|
||||
"""Initialize the private key with the given scalar on the given
|
||||
curve."""
|
||||
self._seed = None
|
||||
self._scalar = scalar
|
||||
self._curve = curve
|
||||
self._pubkey = ECPublicKey(self._scalar * self._curve.G)
|
||||
|
||||
@property
|
||||
def scalar(self):
|
||||
"""Returns the private scalar d of the key."""
|
||||
return self._scalar
|
||||
|
||||
@property
|
||||
def curve(self):
|
||||
"""Returns the group which is used for EC computations."""
|
||||
return self._curve
|
||||
|
||||
@property
|
||||
def pubkey(self):
|
||||
"""Returns the public key that is the counterpart to this private key."""
|
||||
return self._pubkey
|
||||
|
||||
@property
|
||||
def seed(self):
|
||||
"""Returns the seed or None if there wasn't one. A seed is used for
|
||||
schemes like EdDSA; it basically is a binary string that is hashed to
|
||||
yield that actual private scalar d."""
|
||||
return self._seed
|
||||
|
||||
def set_seed(self, seed):
|
||||
"""Sets the seed of the private key. This operation can only performed
|
||||
if no scalar has previously been set for this key."""
|
||||
assert(self._seed is None)
|
||||
self._seed = seed
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def generate(curve):
|
||||
"""Generate a random private key on a given curve."""
|
||||
scalar = secure_rand_int_between(1, curve.n - 1)
|
||||
return ECPrivateKey(scalar, curve)
|
||||
|
||||
def __str__(self):
|
||||
if self._seed is None:
|
||||
return "PrivateKey<d = 0x%x>" % (self.scalar)
|
||||
else:
|
||||
seedstr = "".join("%02x" % (c) for c in self._seed)
|
||||
return "PrivateKey<d = 0x%x, seed = %s>" % (self.scalar, seedstr)
|
44
toyecc/ECPublicKey.py
Normal file
44
toyecc/ECPublicKey.py
Normal file
@ -0,0 +1,44 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2016 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
from .PubKeyOps import PubKeyOpECDSAVerify, PubKeyOpECDSAExploitReusedNonce, PubKeyOpEDDSAVerify, PubKeyOpEDDSAEncode, PubKeyOpECIESEncrypt, PubKeyOpLoad
|
||||
|
||||
class ECPublicKey(PubKeyOpECDSAVerify, PubKeyOpECDSAExploitReusedNonce, PubKeyOpEDDSAVerify, PubKeyOpEDDSAEncode, PubKeyOpECIESEncrypt, PubKeyOpLoad):
|
||||
"""Elliptic curve public key abstraction. An EC public key is just a point
|
||||
on the curve, which is why the constructor only takes this (public) point
|
||||
as a parameter. The public key abstraction allows this point to be used in
|
||||
various meaningful purposes (ECDSA signature verification, etc.)."""
|
||||
|
||||
def __init__(self, point):
|
||||
self._point = point
|
||||
|
||||
@property
|
||||
def curve(self):
|
||||
return self._point.curve
|
||||
|
||||
@property
|
||||
def point(self):
|
||||
return self._point
|
||||
|
||||
def __str__(self):
|
||||
return "PublicKey<%s>" % (str(self.point))
|
202
toyecc/EllipticCurve.py
Normal file
202
toyecc/EllipticCurve.py
Normal file
@ -0,0 +1,202 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
from .AffineCurvePoint import AffineCurvePoint
|
||||
|
||||
class EllipticCurve(object):
|
||||
"""Elliptic curve base class. Provides functionality which all curves have
|
||||
in common."""
|
||||
def __init__(self, p, n, h, Gx, Gy, **kwargs):
|
||||
assert(isinstance(p, int)) # Modulus
|
||||
assert((n is None) or isinstance(n, int)) # Order
|
||||
assert((h is None) or isinstance(h, int)) # Cofactor
|
||||
assert((Gx is None) or isinstance(Gx, int)) # Generator Point X
|
||||
assert((Gy is None) or isinstance(Gy, int)) # Generator Point Y
|
||||
assert((Gx is None) == (Gy is None)) # Either both X and Y of G are set or none
|
||||
self._p = p
|
||||
self._n = n
|
||||
self._h = h
|
||||
if (Gx is not None) and (Gy is not None):
|
||||
self._G = AffineCurvePoint(Gx, Gy, self)
|
||||
else:
|
||||
self._G = None
|
||||
|
||||
if "quirks" in kwargs:
|
||||
self._quirks = { quirk.identifier: quirk for quirk in kwargs["quirks"] }
|
||||
else:
|
||||
self._quirks = { }
|
||||
@property
|
||||
def p(self):
|
||||
"""Returns the prime modulus which constitutes the finite field in
|
||||
which the curve lies."""
|
||||
return self._p
|
||||
|
||||
@property
|
||||
def n(self):
|
||||
"""Returns the order of the subgroup that is created by the generator
|
||||
G."""
|
||||
return self._n
|
||||
|
||||
@property
|
||||
def h(self):
|
||||
"""Returns the cofactor of the generator subgroup, i.e. h = #E(F_p) /
|
||||
n. This will always be an integer according to Lagrange's Theorem."""
|
||||
return self._h
|
||||
|
||||
@property
|
||||
def G(self):
|
||||
"""Returns the generator point G of the curve or None if no such point
|
||||
was set. The generator point generates a subgroup over #E(F_p)."""
|
||||
return self._G
|
||||
|
||||
@property
|
||||
def curve_order(self):
|
||||
"""Returns the order of the curve in the underlying field, i.e.
|
||||
#E(F_p). Intuitively, this is the total number of points on the curve
|
||||
(plus maybe points at ininity, depending on the curve type) that
|
||||
satisfy the curve equation."""
|
||||
if (self.h is None) or (self.n is None):
|
||||
raise Exception("#E(F_p) is unknown for this curve")
|
||||
return self.h * self.n
|
||||
|
||||
@property
|
||||
def frobenius_trace(self):
|
||||
"""Returns the Frobenius trace 't' of the curve. Since
|
||||
#E(F_p) = p + 1 - t it follows that t = p + 1 - #E(F_p)."""
|
||||
return self.p + 1 - self.curve_order
|
||||
|
||||
@property
|
||||
def domainparams(self):
|
||||
"""Returns the curve parameters as a named tuple."""
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
@property
|
||||
def hasgenerator(self):
|
||||
"""Returns if a generator point was supplied for the curve."""
|
||||
return self.G is not None
|
||||
|
||||
@property
|
||||
def hasname(self):
|
||||
"""Returns if the curve is named (i.e. its name is not None)."""
|
||||
return self.name is not None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Returns the name of the curve, if it was given one during
|
||||
construction. Purely informational."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def prettyname(self):
|
||||
"""Returns the pretty name of the curve type. This might depend on the
|
||||
actual curve, since it may also vary on the actual domain parameters to
|
||||
include if the curve is a Koblitz curve or not."""
|
||||
return self.pretty_name
|
||||
|
||||
@property
|
||||
def curvetype(self):
|
||||
"""Returns a string that corresponds to the curve type. For example,
|
||||
this string can be 'shortweierstrass', 'twistededwards' or
|
||||
'montgomery'."""
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
@property
|
||||
def domainparamdict(self):
|
||||
"""Returns the domain parameters of the curve as a dictionary."""
|
||||
return dict(self.domainparams._asdict())
|
||||
|
||||
@property
|
||||
def security_bit_estimate(self):
|
||||
"""Gives a haphazard estimate of the security of the underlying field,
|
||||
in bits. For most curves, this will be half the bitsize of n (but might
|
||||
be less, for example for Koblitz curves some bits might be
|
||||
subtracted)."""
|
||||
return self.n.bit_length() // 2
|
||||
|
||||
def enumerate_points(self):
|
||||
"""Enumerates all points on the curve, including the point at infinity
|
||||
(if the curve has such a special point)."""
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
def naive_order_calculation(self):
|
||||
"""Naively calculates the order #E(F_p) of the curve by enumerating and
|
||||
counting all points which fulfull the curve equation. Note that this
|
||||
implementation only works for the smallest of curves and is
|
||||
computationally infeasible for all practical applications."""
|
||||
order = 0
|
||||
for pt in self.enumerate_points():
|
||||
order += 1
|
||||
return order
|
||||
|
||||
def neutral(self):
|
||||
"""Returns the neutral element of the curve group (for some curves,
|
||||
this will be the point at infinity)."""
|
||||
return AffineCurvePoint(None, None, self)
|
||||
|
||||
def is_neutral(self, P):
|
||||
"""Checks if a given point P is the neutral element of the group."""
|
||||
return P.x is None
|
||||
|
||||
def oncurve(self, P):
|
||||
"""Checks is a given point P is on the curve."""
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
def point_addition(self, P, Q):
|
||||
"""Returns the sum of two points P and Q on the curve."""
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
def point_conjugate(self, P):
|
||||
"""Returns the negated point -P to a given point P."""
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
def compress(self, P):
|
||||
"""Returns the compressed representation of the point P on the
|
||||
curve. Not all curves may support this operation."""
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
def uncompress(self, compressed):
|
||||
"""Returns the uncompressed representation of a point on the curve. Not
|
||||
all curves may support this operation."""
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
def has_quirk(self, quirk_class):
|
||||
"""Some elliptic curves may have quirks or tweaks for certain
|
||||
algorithms. These are attached to the curve using the 'quirks' kwarg of
|
||||
the constructor. Code that wants to query if a specific quirk is
|
||||
present may do so by calling 'has_quirk' with the according quirk class
|
||||
(not a quirk class instance!)."""
|
||||
return quirk_class.identifier in self._quirks
|
||||
|
||||
def get_quirk(self, quirk_class):
|
||||
"""If a quirk is present for a given elliptic curve, this quirk may
|
||||
have been parametrized during instanciation. The get_quirk() method
|
||||
returns that quirk instance when given a specific quirk class as input.
|
||||
It raises a KeyError if the requested quirk is not present for the
|
||||
elliptic curve."""
|
||||
return self._quirks[quirk_class.identifier]
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.domainparams == other.domainparams
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
34
toyecc/Exceptions.py
Normal file
34
toyecc/Exceptions.py
Normal file
@ -0,0 +1,34 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
class DuplicateCurveException(Exception):
|
||||
pass
|
||||
|
||||
class NoSuchCurveException(Exception):
|
||||
pass
|
||||
|
||||
class UnsupportedPointFormatException(Exception):
|
||||
pass
|
||||
|
||||
class UnsupportedFieldException(Exception):
|
||||
pass
|
249
toyecc/FieldElement.py
Normal file
249
toyecc/FieldElement.py
Normal file
@ -0,0 +1,249 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
import random
|
||||
|
||||
class FieldElement(object):
|
||||
"""Represents an element in a finite field over a (prime) modulus."""
|
||||
|
||||
def __init__(self, intvalue, modulus):
|
||||
assert(isinstance(intvalue, int))
|
||||
assert(isinstance(modulus, int))
|
||||
self._intvalue = intvalue % modulus
|
||||
self._modulus = modulus
|
||||
self._qnr = None
|
||||
|
||||
@property
|
||||
def modulus(self):
|
||||
"""Returns the field's modulus."""
|
||||
return self._modulus
|
||||
|
||||
@staticmethod
|
||||
def _eea(a, b):
|
||||
"""Extended euclidian algorithm. Returns the gcd of (a, b) and the
|
||||
Bezout-coefficients."""
|
||||
assert(isinstance(a, int))
|
||||
assert(isinstance(b, int))
|
||||
(s, t, u, v) = (1, 0, 0, 1)
|
||||
while b != 0:
|
||||
(q, r) = (a // b, a % b)
|
||||
(unew, vnew) = (s, t)
|
||||
s = u - (q * s)
|
||||
t = v - (q * t)
|
||||
(a, b) = (b, r)
|
||||
(u, v) = (unew, vnew)
|
||||
return (a, u, v)
|
||||
|
||||
def inverse(self):
|
||||
if int(self) == 0:
|
||||
raise Exception("Trying to invert zero")
|
||||
(gcd, u, v) = self._eea(int(self), self.modulus)
|
||||
return FieldElement(v, self.modulus)
|
||||
|
||||
@property
|
||||
def is_qr(self):
|
||||
"""Returns if the number is a quadratic residue according to Euler's
|
||||
criterion."""
|
||||
return not self.is_qnr
|
||||
|
||||
@property
|
||||
def is_qnr(self):
|
||||
"""Returns if the number is a quadratic non-residue according to
|
||||
Euler's criterion."""
|
||||
if self._qnr is None:
|
||||
self._qnr = int(self ** ((self._modulus - 1) // 2)) != 1
|
||||
return self._qnr
|
||||
|
||||
@property
|
||||
def legrende_symbol(self):
|
||||
"""Returns the Legrende symbol of the field element, i.e. 0 if the
|
||||
element is 0 mod p, 1 if it is a quadratic residue mod p or -1 if it is
|
||||
a quadratic non-residue mod p."""
|
||||
if self == 0:
|
||||
return 0
|
||||
elif self.is_qr:
|
||||
return 1
|
||||
else:
|
||||
return -1
|
||||
|
||||
def _tonelli_shanks_sqrt(self):
|
||||
"""Performs the Tonelli-Shanks algorithm to determine the square root
|
||||
on an element. Note that the algorithm only works if the value it is
|
||||
performed on is a quadratic residue mod p."""
|
||||
q = self._modulus - 1
|
||||
s = 0
|
||||
while (q % 2) == 0:
|
||||
s += 1
|
||||
q >>= 1
|
||||
assert(q * (2 ** s) == self.modulus - 1)
|
||||
|
||||
while True:
|
||||
z = FieldElement(random.randint(1, self.modulus - 1), self.modulus)
|
||||
if z.is_qnr:
|
||||
break
|
||||
assert(z.is_qnr)
|
||||
c = z ** q
|
||||
|
||||
r = self ** ((q + 1) // 2)
|
||||
t = self ** q
|
||||
m = s
|
||||
while int(t) != 1:
|
||||
for i in range(1, m):
|
||||
if int(t ** (1 << i)) == 1:
|
||||
break
|
||||
|
||||
b = c ** (1 << (m - i - 1))
|
||||
r = r * b
|
||||
t = t * (b ** 2)
|
||||
c = b ** 2
|
||||
m = i
|
||||
|
||||
return r
|
||||
|
||||
def sqr(self):
|
||||
"""Return the squared value."""
|
||||
return self * self
|
||||
|
||||
def sqrt(self):
|
||||
"""Returns the square root of the value or None if the value is a
|
||||
quadratic non-residue mod p."""
|
||||
if self.is_qnr:
|
||||
return None
|
||||
|
||||
if (self._modulus % 4) == 3:
|
||||
root = self ** ((self._modulus + 1) // 4)
|
||||
assert(root * root == self)
|
||||
else:
|
||||
root = self._tonelli_shanks_sqrt()
|
||||
|
||||
if (int(root) & 1) == 0:
|
||||
return (root, -root)
|
||||
else:
|
||||
return (-root, root)
|
||||
|
||||
def quartic_root(self):
|
||||
"""Returns the quartic root of the value or None if no such value
|
||||
explicitly exists mod p."""
|
||||
root = self.sqrt()
|
||||
if root is not None:
|
||||
r1 = root[0].sqrt() or list()
|
||||
r2 = root[1].sqrt() or list()
|
||||
for candidate in list(r1) + list(r2):
|
||||
if (candidate ** 4) == self:
|
||||
return candidate
|
||||
|
||||
def __checktype(self, value):
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
elif isinstance(value, FieldElement):
|
||||
if value.modulus == self.modulus:
|
||||
return int(value)
|
||||
else:
|
||||
raise Exception("Cannot perform meaningful arithmetic operations on field elements in different fields.")
|
||||
|
||||
def sigint(self):
|
||||
"""Returns a signed integer if the negative value is less than 10
|
||||
decimal digits and the absolute negated value is smaller than the
|
||||
absolute positive value."""
|
||||
neg = abs(int(-self))
|
||||
if (neg < int(self)) and (neg < 1000000000):
|
||||
return -neg
|
||||
else:
|
||||
return int(self)
|
||||
|
||||
@classmethod
|
||||
def any_qnr(cls, modulus):
|
||||
"""Returns any quadratic non-residue in F(modulus)."""
|
||||
for i in range(1000):
|
||||
candidate = cls(random.randint(2, modulus - 1), modulus)
|
||||
if candidate.is_qnr:
|
||||
return candidate
|
||||
raise Exception("Could not find a QNR in F_%d with a reasonable amount of tries." % (modulus))
|
||||
|
||||
def __int__(self):
|
||||
return self._intvalue
|
||||
|
||||
def __add__(self, value):
|
||||
value = self.__checktype(value)
|
||||
if value is None:
|
||||
return NotImplemented
|
||||
return FieldElement(int(self) + value, self.modulus)
|
||||
|
||||
def __sub__(self, value):
|
||||
value = self.__checktype(value)
|
||||
if value is None:
|
||||
return NotImplemented
|
||||
return FieldElement(int(self) - value, self.modulus)
|
||||
|
||||
def __mul__(self, value):
|
||||
value = self.__checktype(value)
|
||||
if value is None:
|
||||
return NotImplemented
|
||||
return FieldElement(int(self) * value, self.modulus)
|
||||
|
||||
def __floordiv__(self, value):
|
||||
value = self.__checktype(value)
|
||||
if value is None:
|
||||
return NotImplemented
|
||||
return self * FieldElement(value, self.modulus).inverse()
|
||||
|
||||
def __pow__(self, exponent):
|
||||
assert(isinstance(exponent, int))
|
||||
return FieldElement(pow(int(self), exponent, self.modulus), self.modulus)
|
||||
|
||||
def __neg__(self):
|
||||
return FieldElement(-int(self), self.modulus)
|
||||
|
||||
def __radd__(self, value):
|
||||
return self + value
|
||||
|
||||
def __rsub__(self, value):
|
||||
return -self + value
|
||||
|
||||
def __rmul__(self, value):
|
||||
return self * value
|
||||
|
||||
def __rfloordiv__(self, value):
|
||||
return self.inverse() * value
|
||||
|
||||
def __eq__(self, value):
|
||||
if value is None:
|
||||
return False
|
||||
value = self.__checktype(value)
|
||||
return int(self) == (value % self.modulus)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __lt__(self, value):
|
||||
value = self.__checktype(value)
|
||||
return int(self) < value
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self._intvalue, self._modulus))
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def __str__(self):
|
||||
return "{0x%x}" % (int(self))
|
164
toyecc/MontgomeryCurve.py
Normal file
164
toyecc/MontgomeryCurve.py
Normal file
@ -0,0 +1,164 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
import collections
|
||||
from .FieldElement import FieldElement
|
||||
from .AffineCurvePoint import AffineCurvePoint
|
||||
from .EllipticCurve import EllipticCurve
|
||||
from .DocInherit import doc_inherit
|
||||
import toyecc.TwistedEdwardsCurve
|
||||
|
||||
_MontgomeryCurveDomainParameters = collections.namedtuple("MontgomeryCurveDomainParameters", [ "curvetype", "a", "b", "p", "n", "G" ])
|
||||
|
||||
class MontgomeryCurve(EllipticCurve):
|
||||
"""Represents an elliptic curve over a finite field F_P that satisfies the
|
||||
Montgomery equation by^2 = x^3 + ax^2 + x."""
|
||||
pretty_name = "Montgomery"
|
||||
|
||||
def __init__(self, a, b, p, n, h, Gx, Gy, **kwargs):
|
||||
"""Create an elliptic Montgomery curve given the equation coefficients
|
||||
a and b, the curve modulus p, the order of the curve n, the cofactor of
|
||||
the curve h and the generator point G's X and Y coordinates in affine
|
||||
representation, Gx and Gy."""
|
||||
EllipticCurve.__init__(self, p, n, h, Gx, Gy, **kwargs)
|
||||
assert(isinstance(a, int)) # Curve coefficent A
|
||||
assert(isinstance(b, int)) # Curve coefficent B
|
||||
self._a = FieldElement(a, p)
|
||||
self._b = FieldElement(b, p)
|
||||
self._name = kwargs.get("name")
|
||||
|
||||
# Check that the curve is not singular
|
||||
assert(self.b * ((self.a ** 2) - 4) != 0)
|
||||
|
||||
if self._G is not None:
|
||||
# Check that the generator G is on the curve
|
||||
assert(self._G.oncurve())
|
||||
|
||||
# Check that the generator G is of curve order
|
||||
assert((self.n * self.G).is_neutral)
|
||||
|
||||
@property
|
||||
@doc_inherit(EllipticCurve)
|
||||
def domainparams(self):
|
||||
return _MontgomeryCurveDomainParameters(curvetype = self.curvetype, a = self.a, b = self.b, p = self.p, n = self.n, G = self.G)
|
||||
|
||||
@property
|
||||
@doc_inherit(EllipticCurve)
|
||||
def curvetype(self):
|
||||
return "montgomery"
|
||||
|
||||
@property
|
||||
def a(self):
|
||||
"""Returns the coefficient a of the curve equation by^2 = x^3 + ax^2 + x."""
|
||||
return self._a
|
||||
|
||||
@property
|
||||
def b(self):
|
||||
"""Returns the coefficient b of the curve equation by^2 = x^3 + ax^2 + x."""
|
||||
return self._b
|
||||
|
||||
@doc_inherit(EllipticCurve)
|
||||
def oncurve(self, P):
|
||||
return (P.is_neutral) or ((self.b * P.y ** 2) == (P.x ** 3) + (self.a * (P.x ** 2)) + P.x)
|
||||
|
||||
@doc_inherit(EllipticCurve)
|
||||
def point_conjugate(self, P):
|
||||
return AffineCurvePoint(int(P.x), int(-P.y), self)
|
||||
|
||||
@doc_inherit(EllipticCurve)
|
||||
def point_addition(self, P, Q):
|
||||
if P.is_neutral:
|
||||
# P is at infinity, O + Q = Q
|
||||
result = Q
|
||||
elif P == -Q:
|
||||
# P == -Q, return O (point at infinity)
|
||||
result = AffineCurvePoint.neutral(self)
|
||||
elif P == Q:
|
||||
# P == Q, point doubling
|
||||
newx = -2 * P.x - self.a + (3 * P.x**2 + 2 * P.x * self.a + 1)**2 // (4 * P.y**2 * self.b)
|
||||
newy = -P.y + (3 * P.x**2 + 2 * P.x * self.a + 1) * (3 * P.x + self.a) // (2 * P.y * self.b) - (3 * P.x**2 + 2 * P.x * self.a + 1)**3 // (8 * P.y**3 * self.b**2)
|
||||
result = AffineCurvePoint(int(newx), int(newy), self)
|
||||
else:
|
||||
# P != Q, point addition
|
||||
newx = -P.x - Q.x - self.a + (P.y - Q.y)**2 * self.b // (P.x - Q.x)**2
|
||||
newy = (2 * P.x + Q.x + self.a) * (P.y - Q.y) // (P.x - Q.x) - P.y - (P.y - Q.y)**3 * self.b // (P.x - Q.x)**3
|
||||
result = AffineCurvePoint(int(newx), int(newy), self)
|
||||
return result
|
||||
|
||||
def to_twistededwards(self, a = None):
|
||||
"""Converts the domain parameters of this curve to domain parameters of
|
||||
a birationally equivalent twisted Edwards curve. The user may select a
|
||||
desired a coefficient that the resulting Edwards curve shall have or
|
||||
leave it at None to accept an arbitrary one."""
|
||||
assert((a is None) or isinstance(a, int))
|
||||
|
||||
# For the Montgomery curve, B can always be arbitrarily chosen as long
|
||||
# as the surrogate B coeffients are identical in their quadratic
|
||||
# residue property mod p. This means an Montgomery curve where B is a
|
||||
# quadratic residue mod p is isomorphous to all other Montgomery curves
|
||||
# with identical A, p and where B is also a quadratic residue mod p. We
|
||||
# use this property to get the curve we want if there is a desired "a"
|
||||
# outcome and choose B appropriately.
|
||||
if a is None:
|
||||
# No special wish for a, just do the normal conversion
|
||||
conversion_b = self.b
|
||||
a = (self.a + 2) // conversion_b
|
||||
else:
|
||||
# We desire a special a and calculate the B we want
|
||||
conversion_b = (self.a + 2) // a
|
||||
|
||||
# And assure that it's QR property is the same as the original
|
||||
assert(conversion_b.is_qr == self.b.is_qr)
|
||||
d = (self.a - 2) // conversion_b
|
||||
|
||||
# Then construct a curve with no generator first
|
||||
raw_curve = toyecc.TwistedEdwardsCurve.TwistedEdwardsCurve(
|
||||
a = int(a),
|
||||
d = int(d),
|
||||
p = self.p,
|
||||
n = self.n,
|
||||
h = self.h,
|
||||
Gx = None,
|
||||
Gy = None,
|
||||
)
|
||||
|
||||
# Convert the generator point to the new curve
|
||||
G_twed = self.G.convert(raw_curve)
|
||||
|
||||
# And recreate the curve with this new generator
|
||||
twed_curve = toyecc.TwistedEdwardsCurve.TwistedEdwardsCurve(
|
||||
a = int(a),
|
||||
d = int(d),
|
||||
p = self.p,
|
||||
n = self.n,
|
||||
h = self.h,
|
||||
Gx = int(G_twed.x),
|
||||
Gy = int(G_twed.y),
|
||||
)
|
||||
return twed_curve
|
||||
|
||||
def __str__(self):
|
||||
if self.hasname:
|
||||
return "MontgomeryCurve<%s>" % (self.name)
|
||||
else:
|
||||
return "MontgomeryCurve<0x%x y^2 = x^3 + 0x%x x^2 + x mod 0x%x>" % (int(self.b), int(self.a), int(self.p))
|
258
toyecc/PointOps.py
Normal file
258
toyecc/PointOps.py
Normal file
@ -0,0 +1,258 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
from . import Tools
|
||||
from .FieldElement import FieldElement
|
||||
from .Exceptions import UnsupportedPointFormatException
|
||||
|
||||
class PointOpEDDSAEncoding(object):
|
||||
def eddsa_encode(self):
|
||||
"""Performs serialization of the point as required by EdDSA."""
|
||||
coordlen = (self.curve.B + 7) // 8
|
||||
bitlen = (coordlen * 8) - 1
|
||||
enc_value = int(self.y)
|
||||
enc_value &= ((1 << bitlen) - 1)
|
||||
enc_value |= (int(self.x) & 1) << bitlen
|
||||
return Tools.inttobytes_le(enc_value, (self.curve.B + 7) // 8)
|
||||
|
||||
@staticmethod
|
||||
def __eddsa_recoverx(curve, y):
|
||||
x = 0
|
||||
xx = (y * y - 1) // (curve.d * y * y - curve.a)
|
||||
if curve.p % 8 == 5:
|
||||
x = xx ** ((curve.p + 3) // 8)
|
||||
if x * x == -xx:
|
||||
x = x * (FieldElement(2, curve.p) ** ((curve.p - 1) // 4))
|
||||
elif curve.p % 4 == 3:
|
||||
x = xx ** ((curve.p + 1) // 4)
|
||||
if x * x != xx:
|
||||
x = 0
|
||||
return int(x)
|
||||
|
||||
@classmethod
|
||||
def eddsa_decode(cls, curve, data):
|
||||
"""Performs deserialization of the point as required by EdDSA."""
|
||||
assert(curve.curvetype == "twistededwards")
|
||||
coordlen = (curve.B + 7) // 8
|
||||
bitlen = (coordlen * 8) - 1
|
||||
enc_value = int.from_bytes(data, byteorder = "little")
|
||||
y = enc_value & ((1 << bitlen) - 1)
|
||||
x = PointOpEDDSAEncoding.__eddsa_recoverx(curve, y)
|
||||
hibit = (enc_value >> bitlen) & 1
|
||||
if (x & 1) != hibit:
|
||||
x = curve.p - x
|
||||
return cls(x, y, curve)
|
||||
|
||||
class PointOpCurveConversion(object):
|
||||
@staticmethod
|
||||
def __pconv_twed_mont_scalefactor(twedcurve, montcurve):
|
||||
native_b = 4 // (twedcurve.a - twedcurve.d)
|
||||
if native_b == montcurve.b:
|
||||
# Scaling is not necessary, already native curve format
|
||||
scale_factor = 1
|
||||
else:
|
||||
# Scaling of montgomery y component (v) is needed
|
||||
if twedcurve.hasgenerator and montcurve.hasgenerator:
|
||||
# Convert the generator point of the twisted edwards source
|
||||
# point to unscaled Montgomery space
|
||||
Gv = (1 + twedcurve.G.y) // ((1 - twedcurve.G.y) * twedcurve.G.x)
|
||||
|
||||
# And calculate a multiplicative scaling factor so that the
|
||||
# point will result in the target curve's generator point Y
|
||||
scale_factor = montcurve.G.y // Gv
|
||||
|
||||
elif native_b.is_qr:
|
||||
# If b is a quadradic residue mod p then any other
|
||||
# quadratic residue can serve as a surrgate b coefficient
|
||||
# to yield an isomorphous curve. Only y coordinate of the
|
||||
# resulting points needs to be scaled. Calculate a scaling
|
||||
# ratio.
|
||||
scale_factors = (montcurve.b // native_b).sqrt()
|
||||
|
||||
# At least one of the curves lacks a generator point,
|
||||
# select just any scale factor
|
||||
scale_factor = scale_factors[0].inverse()
|
||||
|
||||
else:
|
||||
# Native B is a quadratic non-residue module B; Not sure
|
||||
# how to handle this case
|
||||
# TODO: Implement this
|
||||
raise Exception(NotImplemented)
|
||||
return scale_factor
|
||||
|
||||
def convert(self, targetcurve):
|
||||
"""Convert the affine curve point to a point on a birationally
|
||||
equivalent target curve."""
|
||||
|
||||
if self.is_neutral:
|
||||
return targetcurve.neutral()
|
||||
|
||||
if (self.curve.curvetype == "twistededwards") and (targetcurve.curvetype == "montgomery"):
|
||||
# (x, y) are Edwards coordinates
|
||||
# (u, v) are Montgomery coordonates
|
||||
(x, y) = (self.x, self.y)
|
||||
u = (1 + y) // (1 - y)
|
||||
v = (1 + y) // ((1 - y) * x)
|
||||
|
||||
# Montgomery coordinates are unscaled to the actual B coefficient
|
||||
# of the curve right now. Calculate scaling factor and scale v
|
||||
# appropriately
|
||||
scaling_factor = self.__pconv_twed_mont_scalefactor(self.curve, targetcurve)
|
||||
v = v * scaling_factor
|
||||
|
||||
point = self.__class__(int(u), int(v), targetcurve)
|
||||
elif (self.curve.curvetype == "montgomery") and (targetcurve.curvetype == "twistededwards"):
|
||||
# (x, y) are Edwards coordinates
|
||||
# (u, v) are Montgomery coordonates
|
||||
(u, v) = (self.x, self.y)
|
||||
y = (u - 1) // (u + 1)
|
||||
x = -(1 + y) // (v * (y - 1))
|
||||
|
||||
# Twisted Edwards coordinates are unscaled to the actual B
|
||||
# coefficient of the curve right now. Calculate scaling factor and
|
||||
# scale x appropriately
|
||||
scaling_factor = self.__pconv_twed_mont_scalefactor(targetcurve, self.curve)
|
||||
x = x * scaling_factor
|
||||
|
||||
point = self.__class__(int(x), int(y), targetcurve)
|
||||
else:
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
assert(point.oncurve())
|
||||
return point
|
||||
|
||||
class PointOpNaiveOrderCalculation(object):
|
||||
def naive_order_calculation(self):
|
||||
"""Calculates the order of the point naively, i.e. by walking through
|
||||
all points until the given neutral element is hit. Note that this only
|
||||
works for smallest of curves and is not computationally feasible for
|
||||
anything else."""
|
||||
curpt = self
|
||||
order = 1
|
||||
while not curpt.is_neutral:
|
||||
order += 1
|
||||
curpt += self
|
||||
return order
|
||||
|
||||
|
||||
class PointOpSerialization(object):
|
||||
def serialize_uncompressed(self):
|
||||
"""Serializes the point into a bytes object in uncompressed form."""
|
||||
length = (self.curve.p.bit_length() + 7) // 8
|
||||
serialized = bytes([ 0x04 ]) + Tools.inttobytes(int(self.x), length) + Tools.inttobytes(int(self.y), length)
|
||||
return serialized
|
||||
|
||||
@classmethod
|
||||
def deserialize_uncompressed(cls, data, curve = None):
|
||||
"""Deserializes a curve point which is given in uncompressed form. A
|
||||
curve may be passed with the 'curve' argument in which case an
|
||||
AffineCurvePoint is returned from this method. Otherwise the affine X
|
||||
and Y coordinates are returned as a tuple."""
|
||||
if data[0] != 0x04:
|
||||
raise UnsupportedPointFormatException("Generator point of explicitly encoded curve is given in unsupported form (0x%x)." % (data[0]))
|
||||
data = data[1:]
|
||||
assert((len(data) % 2) == 0)
|
||||
Px = Tools.bytestoint(data[ : len(data) // 2])
|
||||
Py = Tools.bytestoint(data[len(data) // 2 : ])
|
||||
if curve is not None:
|
||||
return cls(Px, Py, curve)
|
||||
else:
|
||||
return (Px, Py)
|
||||
|
||||
class PointOpScalarMultiplicationXOnly():
|
||||
"""Compute an X-only ladder scalar multiplication of the private key and
|
||||
the X coordinate of a given point."""
|
||||
def _x_double(self, x):
|
||||
"""Doubling of point with coordinate x."""
|
||||
if x is None:
|
||||
return None
|
||||
|
||||
den = 4 * (x**3 + self.curve.a * x + self.curve.b)
|
||||
if den == 0:
|
||||
# Point at infinity
|
||||
return None
|
||||
num = (x**2 - self.curve.a)**2 - (8 * self.curve.b * x)
|
||||
return num // den
|
||||
|
||||
def _x_add_multiplicative(self, x1, x2, x3prime):
|
||||
"""Multiplicative formula addition of x1 + x2, where x3' is the
|
||||
difference in X of P1 - P2. Using this function only makes sense where
|
||||
(P1 - P2) is fixed, as it is in the ladder implementation."""
|
||||
if x1 is None:
|
||||
return x2
|
||||
elif x2 is None:
|
||||
return x1
|
||||
elif x1 == x2:
|
||||
return None
|
||||
num = -4 * self.curve.b * (x1 + x2) + (x1 * x2 - self.curve.a)**2
|
||||
den = x3prime * (x1 - x2)**2
|
||||
result = num // den
|
||||
return result
|
||||
|
||||
def _x_add_additive(self, x1, x2, x3prime):
|
||||
"""Additive formula addition of x1 + x2, where x3' is the difference in
|
||||
X of P1 - P2. Using this function only makes sense where (P1 - P2) is
|
||||
fixed, as it is in the ladder implementation."""
|
||||
if x1 is None:
|
||||
return x2
|
||||
elif x2 is None:
|
||||
return x1
|
||||
elif x1 == x2:
|
||||
return None
|
||||
num = 2 * (x1 + x2) * (x1 * x2 + self.curve.a) + 4 * self.curve.b
|
||||
den = (x1 - x2) ** 2
|
||||
result = num // den - x3prime
|
||||
return result
|
||||
|
||||
def _x_add(self, x1, x2, x3prime):
|
||||
"""There are two equivalent implementations, one using the
|
||||
multiplicative and the other using the additive representation. Both
|
||||
should work equally well."""
|
||||
return self._x_add_multiplicative(x1, x2, x3prime)
|
||||
#return self._x_add_additive(x1, x2, x3prime)
|
||||
|
||||
def scalar_mul_xonly(self, scalar):
|
||||
"""This implements the X-coordinate-only multiplication algorithm of a
|
||||
Short Weierstrass curve with the X coordinate of a given point.
|
||||
Reference is "Izu and Takagi: A Fast Parallel Elliptic Curve
|
||||
Multiplication Resistant against Side Channel Attacks" (2002)"""
|
||||
if self.curve.curvetype != "shortweierstrass":
|
||||
raise NotImplementedError("X-only ladder multiplication is only implemented for Short Weierstrass curves")
|
||||
if self.is_neutral:
|
||||
# Point at infinity is input
|
||||
return None
|
||||
elif scalar == 0:
|
||||
# Multiplication with zero -> point at infinity is output
|
||||
return None
|
||||
|
||||
x_coordinate = int(self.x)
|
||||
if not isinstance(x_coordinate, FieldElement):
|
||||
x_coordinate = FieldElement(x_coordinate, self.curve.p)
|
||||
Q = [ x_coordinate, self._x_double(x_coordinate), None ]
|
||||
for bitno in reversed(range(scalar.bit_length() - 1)):
|
||||
bit = (scalar >> bitno) & 1
|
||||
Q[2] = self._x_double(Q[bit])
|
||||
Q[1] = self._x_add(Q[0], Q[1], x_coordinate)
|
||||
Q[0] = Q[2 - bit]
|
||||
Q[1] = Q[1 + bit]
|
||||
return Q[0]
|
331
toyecc/Polynomial.py
Normal file
331
toyecc/Polynomial.py
Normal file
@ -0,0 +1,331 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
import re
|
||||
import collections
|
||||
|
||||
from .FieldElement import FieldElement
|
||||
|
||||
class _CoeffDict(object):
|
||||
def __init__(self):
|
||||
self._coeffs = { }
|
||||
|
||||
def clone(self):
|
||||
clone = _CoeffDict()
|
||||
clone._coeffs = dict(self._coeffs)
|
||||
return clone
|
||||
|
||||
@property
|
||||
def degree(self):
|
||||
if len(self._coeffs) > 0:
|
||||
return max(self._coeffs.keys())
|
||||
else:
|
||||
return 0
|
||||
|
||||
def clone(self):
|
||||
clone = _CoeffDict()
|
||||
clone._coeffs = dict(self._coeffs)
|
||||
return clone
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._coeffs == other._coeffs
|
||||
|
||||
def __neq__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._coeffs.items())
|
||||
|
||||
def __len__(self):
|
||||
return len(self._coeffs)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._coeffs.get(key, 0)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if (value == 0) and (key in self._coeffs):
|
||||
del self._coeffs[key]
|
||||
else:
|
||||
self._coeffs[key] = value
|
||||
|
||||
def __str__(self):
|
||||
return "CoeffDict<%s>" % (str(self._coeffs))
|
||||
|
||||
class Polynomial(object):
|
||||
_TERM_RE = re.compile("^((?P<coeff>-?\d+)\*)?x(\^(?P<exponent>\d+))?$")
|
||||
_CACHE_EXPONENTS = [ 2, 3 ]
|
||||
|
||||
def __init__(self, modulus, initvalue = None):
|
||||
self._modulus = modulus
|
||||
self._terms = _CoeffDict()
|
||||
if initvalue is None:
|
||||
self._terms[1] = FieldElement(1, self._modulus)
|
||||
else:
|
||||
if initvalue != 0:
|
||||
self._terms[0] = FieldElement(initvalue, self._modulus)
|
||||
self._expcache = { }
|
||||
|
||||
@property
|
||||
def degree(self):
|
||||
return self._terms.degree
|
||||
|
||||
@property
|
||||
def modulus(self):
|
||||
return self._modulus
|
||||
|
||||
@property
|
||||
def is_constant(self):
|
||||
return self.degree == 0
|
||||
|
||||
def get_constant(self):
|
||||
assert(self.is_constant)
|
||||
return self[0]
|
||||
|
||||
def _clone(self):
|
||||
clone = Polynomial(self.modulus, 0)
|
||||
clone._terms = self._terms.clone()
|
||||
return clone
|
||||
|
||||
def substitute(self, value):
|
||||
result = 0
|
||||
for (exponent, coefficient) in self._terms:
|
||||
result += coefficient * (value ** exponent)
|
||||
return result
|
||||
|
||||
def gcd(self, other):
|
||||
"""Returns the greatest common divisor polynomial of this object and
|
||||
the other polynomial."""
|
||||
assert(isinstance(other, Polynomial))
|
||||
assert(self.modulus == other.modulus)
|
||||
assert((self != 0) or (other != 0))
|
||||
(a, b) = (self, other)
|
||||
if a == 0:
|
||||
return b
|
||||
elif b == 0:
|
||||
return a
|
||||
|
||||
while b != 0:
|
||||
(a, b) = (b, a % b)
|
||||
|
||||
highest_coefficient = a._terms[a.degree]
|
||||
a = a // highest_coefficient
|
||||
return a
|
||||
|
||||
def __and__(self, other):
|
||||
"""Returns the greatest common divisor polynomial of this object and
|
||||
the other polynomial."""
|
||||
return self.gcd(other)
|
||||
|
||||
def __add__(self, value):
|
||||
if isinstance(value, int) or isinstance(value, FieldElement):
|
||||
result = self._clone()
|
||||
result._terms[0] += value
|
||||
return result
|
||||
elif isinstance(value, Polynomial):
|
||||
result = self._clone()
|
||||
for (exponent, coefficient) in value:
|
||||
result._terms[exponent] += coefficient
|
||||
return result
|
||||
else:
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
def __sub__(self, value):
|
||||
if isinstance(value, int) or isinstance(value, FieldElement):
|
||||
result = self._clone()
|
||||
result._terms[0] -= value
|
||||
return result
|
||||
elif isinstance(value, Polynomial):
|
||||
result = self._clone()
|
||||
for (exponent, coefficient) in value:
|
||||
result._terms[exponent] -= coefficient
|
||||
return result
|
||||
else:
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
def __pow__(self, value):
|
||||
if value in self._expcache:
|
||||
return self._expcache[value]
|
||||
if isinstance(value, int):
|
||||
if len(self._terms) == 1:
|
||||
result = Polynomial(self.modulus, 0)
|
||||
for (exponent, coefficient) in self:
|
||||
result._terms[exponent * value] = coefficient ** value
|
||||
else:
|
||||
exponent = value
|
||||
result = Polynomial(self.modulus, 1)
|
||||
multiplier = self
|
||||
for bit in range(exponent.bit_length()):
|
||||
if exponent & (1 << bit):
|
||||
result = result * multiplier
|
||||
multiplier = multiplier * multiplier
|
||||
else:
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
if value in self._CACHE_EXPONENTS:
|
||||
self._expcache[value] = result
|
||||
|
||||
return result
|
||||
|
||||
def powmod(self, exponent, modulus):
|
||||
"""Returns the result of (self^exponent) % modulus. Exponent must be an
|
||||
integer and modulus another Polynomial."""
|
||||
assert(isinstance(exponent, int))
|
||||
assert((modulus is None) or isinstance(modulus, Polynomial))
|
||||
assert(exponent >= 0)
|
||||
result = Polynomial(self.modulus, 1)
|
||||
multiplier = self
|
||||
for bit in range(exponent.bit_length()):
|
||||
if exponent & (1 << bit):
|
||||
result = (result * multiplier) % modulus
|
||||
multiplier = (multiplier * multiplier) % modulus
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def parse_poly(cls, polystr, modulus):
|
||||
poly = Polynomial(modulus, 0)
|
||||
|
||||
polystr = polystr.replace(" - ", " + -")
|
||||
terms = polystr.split(" + ")
|
||||
for term in terms:
|
||||
if term.isnumeric():
|
||||
poly._terms[0] += int(term)
|
||||
else:
|
||||
result = cls._TERM_RE.match(term)
|
||||
if result is None:
|
||||
raise Exception("Cannot parse polynomial term: '%s'" % (term))
|
||||
result = result.groupdict()
|
||||
|
||||
result = { key: int(value) for (key, value) in result.items() if (value is not None) }
|
||||
coeff = result.get("coeff", 1)
|
||||
exponent = result.get("exponent", 1)
|
||||
poly._terms[exponent] += FieldElement(coeff, modulus)
|
||||
|
||||
return poly
|
||||
|
||||
def __floordiv__(self, value):
|
||||
if isinstance(value, int) or isinstance(value, FieldElement):
|
||||
result = Polynomial(self.modulus, 0)
|
||||
for (exponent, coefficient) in self:
|
||||
result._terms[exponent] = coefficient // value
|
||||
return result
|
||||
elif isinstance(value, Polynomial):
|
||||
if value.degree == 0:
|
||||
return self // value[0]
|
||||
|
||||
result = Polynomial(self.modulus, 0)
|
||||
numerator = self._clone()
|
||||
while numerator.degree >= value.degree:
|
||||
shift = numerator.degree - value.degree
|
||||
multiplier = numerator[numerator.degree] // value[value.degree]
|
||||
|
||||
result._terms[shift] += multiplier
|
||||
for (exponent, coefficient) in value:
|
||||
numerator._terms[exponent + shift] -= multiplier * coefficient
|
||||
return result
|
||||
|
||||
else:
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
def __mul__(self, value):
|
||||
if isinstance(value, int) or isinstance(value, FieldElement):
|
||||
result = Polynomial(self.modulus, 0)
|
||||
for (exponent, coefficient) in self:
|
||||
result._terms[exponent] = coefficient * value
|
||||
return result
|
||||
elif isinstance(value, Polynomial):
|
||||
result = Polynomial(self.modulus, 0)
|
||||
for (exponent1, coefficient1) in self:
|
||||
for (exponent2, coefficient2) in value:
|
||||
result._terms[exponent1 + exponent2] += coefficient1 * coefficient2
|
||||
return result
|
||||
else:
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
def __mod__(self, value):
|
||||
if isinstance(value, Polynomial):
|
||||
if value.degree == 0:
|
||||
return Polynomial(self.modulus, 0)
|
||||
|
||||
result = self._clone()
|
||||
while result.degree >= value.degree:
|
||||
shift = result.degree - value.degree
|
||||
multiplier = result[result.degree] // value[value.degree]
|
||||
|
||||
for (exponent, coefficient) in value:
|
||||
result._terms[exponent + shift] -= multiplier * coefficient
|
||||
return result
|
||||
else:
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
def __rmul__(self, value):
|
||||
return self * value
|
||||
|
||||
def __radd__(self, value):
|
||||
return self + value
|
||||
|
||||
def __getitem__(self, exponent):
|
||||
return self._terms[exponent]
|
||||
|
||||
def __iter__(self):
|
||||
yield from iter(self._terms)
|
||||
|
||||
def __eq__(self, value):
|
||||
if isinstance(value, int) or isinstance(value, FieldElement):
|
||||
return self.is_constant and (self.get_constant() == value)
|
||||
elif isinstance(value, Polynomial):
|
||||
return (self.modulus == value.modulus) and (self._terms == value._terms)
|
||||
else:
|
||||
raise Exception(NotImplemented)
|
||||
|
||||
def __ne__(self, value):
|
||||
return not (self == value)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def __str__(self):
|
||||
terms = [ ]
|
||||
for (exponent, coefficient) in sorted(self, reverse = True):
|
||||
if coefficient == 0:
|
||||
continue
|
||||
|
||||
if exponent == 0:
|
||||
terms.append("%d" % (int(coefficient)))
|
||||
continue
|
||||
|
||||
elif coefficient == 1:
|
||||
coeffstr = ""
|
||||
else:
|
||||
coeffstr = "%d*" % (int(coefficient))
|
||||
|
||||
if exponent == 1:
|
||||
termstr = "x"
|
||||
else:
|
||||
termstr = "x^%d" % (int(exponent))
|
||||
|
||||
terms.append(coeffstr + termstr)
|
||||
|
||||
if len(terms) == 0:
|
||||
return "0"
|
||||
else:
|
||||
return " + ".join(terms)
|
228
toyecc/PrivKeyOps.py
Normal file
228
toyecc/PrivKeyOps.py
Normal file
@ -0,0 +1,228 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
import hashlib
|
||||
import collections
|
||||
|
||||
from .FieldElement import FieldElement
|
||||
from .Random import secure_rand, secure_rand_int_between
|
||||
from .AffineCurvePoint import AffineCurvePoint
|
||||
from .CurveDB import CurveDB
|
||||
from .ShortWeierstrassCurve import ShortWeierstrassCurve
|
||||
from .ASN1 import parse_asn1_private_key, parse_asn1_field_params_fp
|
||||
from . import Tools
|
||||
from .CurveQuirks import CurveQuirkEdDSASetPrivateKeyMSB, CurveQuirkEdDSAEnsurePrimeOrderSubgroup, CurveQuirkSigningHashFunction
|
||||
|
||||
class PrivKeyOpECDSASign(object):
|
||||
ECDSASignature = collections.namedtuple("ECDSASignature", [ "hashalg", "r", "s" ])
|
||||
|
||||
def ecdsa_sign_hash(self, message_digest, k = None, digestname = None):
|
||||
"""Signs a given messagedigest, given as bytes, using ECDSA.
|
||||
Optionally a nonce k can be supplied which should usually be unqiuely
|
||||
chosen for every ECDSA signature. This way it is possible to
|
||||
deliberately create broken signatures which can be exploited later on.
|
||||
If k is not supplied, it is randomly chosen. If a digestname is
|
||||
supplied the name of this digest eventually ends up in the
|
||||
ECDSASignature object."""
|
||||
assert(isinstance(message_digest, bytes))
|
||||
assert((k is None) or isinstance(k, int))
|
||||
|
||||
# Convert message digest to integer value
|
||||
e = Tools.ecdsa_msgdigest_to_int(message_digest, self.curve.n)
|
||||
|
||||
# Select a random integer (if None is supplied!)
|
||||
if k is None:
|
||||
k = secure_rand_int_between(1, self.curve.n - 1)
|
||||
|
||||
# r = (k * G)_x mod n
|
||||
Rmodp = k * self.curve.G
|
||||
r = int(Rmodp.x) % self.curve.n
|
||||
assert(r != 0)
|
||||
|
||||
s = FieldElement(e + self.scalar * r, self.curve.n) // k
|
||||
|
||||
return self.ECDSASignature(r = r, s = int(s), hashalg = digestname)
|
||||
|
||||
def ecdsa_sign(self, message, digestname, k = None):
|
||||
"""Signs a given message with the digest that is given as a string.
|
||||
Optionally a nonce k can be supplied which should usually be unqiuely
|
||||
chosen for every ECDSA signature. This way it is possible to
|
||||
deliberately create broken signatures which can be exploited later
|
||||
on. If k is not supplied, it is randomly chosen."""
|
||||
assert(isinstance(message, bytes))
|
||||
assert(isinstance(digestname, str))
|
||||
digest_fnc = hashlib.new(digestname)
|
||||
digest_fnc.update(message)
|
||||
message_digest = digest_fnc.digest()
|
||||
return self.ecdsa_sign_hash(message_digest, k = k, digestname = digestname)
|
||||
|
||||
|
||||
class PrivKeyOpECIESDecrypt(object):
|
||||
def ecies_decrypt(self, R):
|
||||
"""Takes the transmitted point R and reconstructs the shared secret
|
||||
point S using the private key."""
|
||||
# Transmitted R is given, restore the symmetric key S
|
||||
return self._scalar * R
|
||||
|
||||
|
||||
class PrivKeyOpEDDSASign(object):
|
||||
class EDDSASignature(object):
|
||||
def __init__(self, curve, R, s):
|
||||
self._curve = curve
|
||||
self._R = R
|
||||
self._s = s
|
||||
|
||||
@property
|
||||
def curve(self):
|
||||
return self._curve
|
||||
|
||||
@property
|
||||
def R(self):
|
||||
return self._R
|
||||
|
||||
@property
|
||||
def s(self):
|
||||
return self._s
|
||||
|
||||
def encode(self):
|
||||
"""Performs serialization of the signature as used by EdDSA."""
|
||||
return self.R.eddsa_encode() + Tools.inttobytes_le(self.s, (self.curve.B + 7) // 8)
|
||||
|
||||
@classmethod
|
||||
def decode(cls, curve, encoded_signature):
|
||||
"""Performs deserialization of the signature as used by EdDSA."""
|
||||
assert(isinstance(encoded_signature, bytes))
|
||||
coordlen = (curve.B + 7) // 8
|
||||
assert(len(encoded_signature) == 2 * coordlen)
|
||||
encoded_R = encoded_signature[:coordlen]
|
||||
encoded_s = encoded_signature[coordlen:]
|
||||
R = AffineCurvePoint.eddsa_decode(curve, encoded_R)
|
||||
s = Tools.bytestoint_le(encoded_s)
|
||||
return cls(curve, R, s)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.R, self.s) == (other.R, other.s)
|
||||
|
||||
def __str__(self):
|
||||
return "EDDSASignature<R = %s, s = %s>" % (self.R, self.s)
|
||||
|
||||
def eddsa_sign(self, message):
|
||||
"""Performs an EdDSA signature of the message. For this to work the
|
||||
curve has to be a twisted Edwards curve and the private key scalar has
|
||||
to be generated from a hashed seed. This hashed seed is automatically
|
||||
generated when a keypair is generated using, for example, the
|
||||
eddsa_generate() function instead of the regular key generation
|
||||
function generate()."""
|
||||
assert(self.curve.curvetype == "twistededwards")
|
||||
if self._seed is None:
|
||||
raise Exception("EdDSA requires a seed which is the source for calculation of the private key scalar.")
|
||||
if not self.curve.has_quirk(CurveQuirkSigningHashFunction):
|
||||
raise Exception("Unable to determine EdDSA signature function.")
|
||||
|
||||
quirk = self.curve.get_quirk(CurveQuirkSigningHashFunction)
|
||||
h = quirk.hashdata(self._seed)
|
||||
|
||||
coordlen = (self.curve.B + 7) // 8
|
||||
r = Tools.bytestoint_le(quirk.hashdata(h[coordlen : 2 * coordlen] + message))
|
||||
R = r * self.curve.G
|
||||
s = (r + Tools.bytestoint_le(quirk.hashdata(R.eddsa_encode() + self.pubkey.point.eddsa_encode() + message)) * self.scalar) % self.curve.n
|
||||
sig = self.EDDSASignature(self.curve, R, s)
|
||||
return sig
|
||||
|
||||
|
||||
class PrivKeyOpEDDSAKeyGen(object):
|
||||
@classmethod
|
||||
def eddsa_generate(cls, curve, seed = None):
|
||||
"""Generates a randomly selected seed value. This seed value is then
|
||||
hashed using the EdDSA hash function (SHA512 for Ed2556 and
|
||||
Shake256-114 for Ed448) and the resulting value is (slightly modified)
|
||||
used as the private key scalar. Since for EdDSA signing operations
|
||||
this seed value is needed, it is also stored within the private key."""
|
||||
coordlen = (curve.B + 7) // 8
|
||||
if seed is None:
|
||||
seed = secure_rand(coordlen)
|
||||
assert(isinstance(seed, bytes))
|
||||
assert(len(seed) == coordlen)
|
||||
|
||||
# Calculate hash over seed and generate scalar from hash over seed
|
||||
if not curve.has_quirk(CurveQuirkSigningHashFunction):
|
||||
raise Exception("Unable to determine EdDSA signature function.")
|
||||
quirk = curve.get_quirk(CurveQuirkSigningHashFunction)
|
||||
h = quirk.hashdata(seed)
|
||||
a = int.from_bytes(h[:coordlen], byteorder = "little") & ((1 << (curve.B - 1)) - 1)
|
||||
|
||||
# Do we need to mask out lower significant bits to ensure that we use a
|
||||
# prime order subgroup?
|
||||
if curve.has_quirk(CurveQuirkEdDSAEnsurePrimeOrderSubgroup):
|
||||
if not Tools.is_power_of_two(curve.h):
|
||||
raise Exception("Can only ensure prime order subgroup by masking 'a' when curve cofactor is a power of two, h = %d isn't." % (curve.h))
|
||||
a &= ~(curve.h - 1)
|
||||
|
||||
# Is the MSB of the curve always set to ensure constant runtime of the
|
||||
# Montgomery ladder?
|
||||
if curve.has_quirk(CurveQuirkEdDSASetPrivateKeyMSB):
|
||||
bit = curve.n.bit_length() + 1
|
||||
a |= (1 << bit)
|
||||
privkey = cls(a, curve)
|
||||
privkey.set_seed(seed)
|
||||
return privkey
|
||||
|
||||
|
||||
class PrivKeyOpEDDSAEncode(object):
|
||||
def eddsa_encode(self):
|
||||
"""Performs serialization of a private key that is used for EdDSA."""
|
||||
return self.seed
|
||||
|
||||
@classmethod
|
||||
def eddsa_decode(cls, curve, encoded_privkey):
|
||||
"""Performs decoding of a serialized private key as it is used for EdDSA."""
|
||||
return cls.eddsa_generate(curve, encoded_privkey)
|
||||
|
||||
|
||||
class PrivKeyOpECDH(object):
|
||||
def ecdh_compute(self, peer_pubkey):
|
||||
"""Compute the shared secret point using our own private key and the
|
||||
public key of our peer."""
|
||||
return self.scalar * peer_pubkey.point
|
||||
|
||||
|
||||
class PrivKeyOpLoad(object):
|
||||
@classmethod
|
||||
def load_derdata(cls, derdata):
|
||||
"""Loads an EC private key from a DER-encoded ASN.1 bytes object."""
|
||||
asn1 = parse_asn1_private_key(derdata)
|
||||
private_key_scalar = Tools.bytestoint(asn1["privateKey"])
|
||||
curve = CurveDB().get_curve_from_asn1(asn1["parameters"])
|
||||
return cls(private_key_scalar, curve)
|
||||
|
||||
@classmethod
|
||||
def load_pem(cls, pemfilename):
|
||||
"""Loads an EC private key from a PEM-encoded 'EC PRIVATE KEY' file."""
|
||||
return cls.load_derdata(Tools.load_pem_data(pemfilename, "EC PRIVATE KEY"))
|
||||
|
||||
@classmethod
|
||||
def load_der(cls, derfilename):
|
||||
"""Loads an EC private key from a DER-encoded ASN.1 file."""
|
||||
with open(derfilename, "rb") as f:
|
||||
data = f.read()
|
||||
return cls.load_derdata(data)
|
166
toyecc/PubKeyOps.py
Normal file
166
toyecc/PubKeyOps.py
Normal file
@ -0,0 +1,166 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
import hashlib
|
||||
|
||||
from .FieldElement import FieldElement
|
||||
from .AffineCurvePoint import AffineCurvePoint
|
||||
from .Random import secure_rand_int_between
|
||||
from . import Tools
|
||||
from .PrivKeyOps import PrivKeyOpLoad
|
||||
from .ASN1 import parse_asn1_public_key
|
||||
from .CurveDB import CurveDB
|
||||
from .CurveQuirks import CurveQuirkSigningHashFunction
|
||||
|
||||
class PubKeyOpECDSAExploitReusedNonce(object):
|
||||
def ecdsa_exploit_reused_nonce(self, msg1, sig1, msg2, sig2):
|
||||
"""Given two different messages msg1 and msg2 and their corresponding
|
||||
signatures sig1, sig2, try to calculate the private key that was used
|
||||
for signing if during signature generation no unique nonces were
|
||||
used."""
|
||||
assert(isinstance(msg1, bytes))
|
||||
assert(isinstance(msg2, bytes))
|
||||
assert(msg1 != msg2)
|
||||
assert(sig1.r == sig2.r)
|
||||
|
||||
# Hash the messages
|
||||
dig1 = hashlib.new(sig1.hashalg)
|
||||
dig1.update(msg1)
|
||||
dig1 = dig1.digest()
|
||||
dig2 = hashlib.new(sig2.hashalg)
|
||||
dig2.update(msg2)
|
||||
dig2 = dig2.digest()
|
||||
|
||||
# Calculate hashes of messages
|
||||
e1 = Tools.ecdsa_msgdigest_to_int(dig1, self.point.curve.n)
|
||||
e2 = Tools.ecdsa_msgdigest_to_int(dig2, self.point.curve.n)
|
||||
|
||||
# Take them modulo n
|
||||
e1 = FieldElement(e1, self.point.curve.n)
|
||||
e2 = FieldElement(e2, self.point.curve.n)
|
||||
|
||||
(s1, s2) = (FieldElement(sig1.s, self.point.curve.n), FieldElement(sig2.s, self.point.curve.n))
|
||||
r = sig1.r
|
||||
|
||||
# Recover (supposedly) random nonce
|
||||
nonce = (e1 - e2) // (s1 - s2)
|
||||
|
||||
# Recover private key
|
||||
priv = ((nonce * s1) - e1) // r
|
||||
|
||||
return { "nonce": nonce, "privatekey": priv }
|
||||
|
||||
|
||||
class PubKeyOpECDSAVerify(object):
|
||||
def ecdsa_verify_hash(self, message_digest, signature):
|
||||
"""Verify ECDSA signature over the hash of a message (the message
|
||||
digest)."""
|
||||
assert(isinstance(message_digest, bytes))
|
||||
assert(0 < signature.r < self.curve.n)
|
||||
assert(0 < signature.s < self.curve.n)
|
||||
|
||||
# Convert message digest to integer value
|
||||
e = Tools.ecdsa_msgdigest_to_int(message_digest, self.curve.n)
|
||||
|
||||
(r, s) = (signature.r, FieldElement(signature.s, self.curve.n))
|
||||
w = s.inverse()
|
||||
u1 = int(e * w)
|
||||
u2 = int(r * w)
|
||||
|
||||
pt = (u1 * self.curve.G) + (u2 * self.point)
|
||||
x1 = int(pt.x) % self.curve.n
|
||||
return x1 == r
|
||||
|
||||
def ecdsa_verify(self, message, signature):
|
||||
"""Verify an ECDSA signature over a message."""
|
||||
assert(isinstance(message, bytes))
|
||||
digest_fnc = hashlib.new(signature.hashalg)
|
||||
digest_fnc.update(message)
|
||||
message_digest = digest_fnc.digest()
|
||||
return self.ecdsa_verify_hash(message_digest, signature)
|
||||
|
||||
|
||||
class PubKeyOpEDDSAVerify(object):
|
||||
def eddsa_verify(self, message, signature):
|
||||
"""Verify an EdDSA signature over a message."""
|
||||
if not self.curve.has_quirk(CurveQuirkSigningHashFunction):
|
||||
raise Exception("Unable to determine EdDSA signature function.")
|
||||
quirk = self.curve.get_quirk(CurveQuirkSigningHashFunction)
|
||||
h = Tools.bytestoint_le(quirk.hashdata(signature.R.eddsa_encode() + self.point.eddsa_encode() + message))
|
||||
return (signature.s * self.curve.G) == signature.R + (h * self.point)
|
||||
|
||||
|
||||
class PubKeyOpEDDSAEncode(object):
|
||||
def eddsa_encode(self):
|
||||
"""Encodes a EdDSA-encoded public key to its serialized (bytes)
|
||||
form."""
|
||||
return self.point.eddsa_encode()
|
||||
|
||||
@classmethod
|
||||
def eddsa_decode(cls, curve, encoded_pubkey):
|
||||
"""Decodes a EdDSA-encoded public key from its serialized (bytes)
|
||||
form."""
|
||||
pubkey = AffineCurvePoint.eddsa_decode(curve, encoded_pubkey)
|
||||
return cls(pubkey)
|
||||
|
||||
class PubKeyOpECIESEncrypt(object):
|
||||
def ecies_encrypt(self, r = None):
|
||||
"""Generates a shared secret which can be used to symetrically encrypt
|
||||
data that only the holder of the corresponding private key can read.
|
||||
The output are two points, R and S: R is the public point that is
|
||||
transmitted together with the message while S is the point which
|
||||
resembles the shared secret. The receiver can use R together with her
|
||||
private key to reconstruct S. A random nonce r can be supplied for this
|
||||
function. If it isn't supplied, it is randomly chosen."""
|
||||
|
||||
# Chose a random number
|
||||
if r is None:
|
||||
r = secure_rand_int_between(1, self.curve.n - 1)
|
||||
|
||||
R = r * self.curve.G
|
||||
S = r * self.point
|
||||
|
||||
# Return the publicly transmitted R and the symmetric key S
|
||||
return { "R": R, "S": S }
|
||||
|
||||
|
||||
class PubKeyOpLoad(object):
|
||||
@classmethod
|
||||
def load_derdata(cls, derdata):
|
||||
"""Loads an EC public key from a DER-encoded ASN.1 bytes object."""
|
||||
asn1 = parse_asn1_public_key(derdata)
|
||||
curve = CurveDB().get_curve_from_asn1(asn1["algorithm"]["parameters"])
|
||||
point = AffineCurvePoint.deserialize_uncompressed(Tools.bits_to_bytes(asn1["subjectPublicKey"]), curve)
|
||||
return cls(point)
|
||||
|
||||
@classmethod
|
||||
def load_pem(cls, pemfilename):
|
||||
"""Loads an EC public key from a PEM-encoded 'PUBLIC KEY' file."""
|
||||
return cls.load_derdata(Tools.load_pem_data(pemfilename, "PUBLIC KEY"))
|
||||
|
||||
@classmethod
|
||||
def load_der(cls, derfilename):
|
||||
"""Loads an EC public key from a DER-encoded ASN.1 file."""
|
||||
with open(derfilename, "rb") as f:
|
||||
data = f.read()
|
||||
return cls.load_derdata(data)
|
48
toyecc/Random.py
Normal file
48
toyecc/Random.py
Normal file
@ -0,0 +1,48 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
def secure_rand(length):
|
||||
import os
|
||||
"""Returns a secure random bytes() object of the length 'length' bytes."""
|
||||
data = os.urandom(length)
|
||||
assert(len(data) == length)
|
||||
return data
|
||||
|
||||
|
||||
def secure_rand_int(max_value):
|
||||
"""Yields a value 0 <= return < maxvalue."""
|
||||
assert(max_value >= 2)
|
||||
bytecnt = ((max_value - 1).bit_length() + 7) // 8
|
||||
max_bin_value = 256 ** bytecnt
|
||||
wholecnt = max_bin_value // max_value
|
||||
cutoff = wholecnt * max_value
|
||||
while True:
|
||||
rnd = sum((value << (8 * bytepos)) for (bytepos, value) in enumerate(secure_rand(bytecnt)))
|
||||
if rnd < cutoff:
|
||||
break
|
||||
return rnd % max_value
|
||||
|
||||
def secure_rand_int_between(min_value, max_value):
|
||||
"""Yields a random number which goes from min_value (inclusive) to
|
||||
max_value (inclusive)."""
|
||||
return secure_rand_int(max_value - min_value + 1) + min_value
|
203
toyecc/ShortWeierstrassCurve.py
Normal file
203
toyecc/ShortWeierstrassCurve.py
Normal file
@ -0,0 +1,203 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2016 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
import collections
|
||||
from .FieldElement import FieldElement
|
||||
from .AffineCurvePoint import AffineCurvePoint
|
||||
from .EllipticCurve import EllipticCurve
|
||||
from .DocInherit import doc_inherit
|
||||
from .CurveOps import CurveOpIsomorphism, CurveOpExportSage
|
||||
|
||||
_ShortWeierstrassCurveDomainParameters = collections.namedtuple("ShortWeierstrassCurveDomainParameters", [ "curvetype", "a", "b", "p", "n", "h", "G" ])
|
||||
|
||||
class ShortWeierstrassCurve(EllipticCurve, CurveOpIsomorphism, CurveOpExportSage):
|
||||
"""Represents an elliptic curve over a finite field F_P that satisfies the
|
||||
short Weierstrass equation y^2 = x^3 + ax + b."""
|
||||
pretty_name = "Short Weierstrass"
|
||||
|
||||
def __init__(self, a, b, p, n, h, Gx, Gy, **kwargs):
|
||||
"""Create an elliptic curve given the equation coefficients a and b,
|
||||
the curve modulus p, the order of the curve n, the cofactor of the
|
||||
curve h and the generator point G's X and Y coordinates in affine
|
||||
representation, Gx and Gy."""
|
||||
EllipticCurve.__init__(self, p, n, h, Gx, Gy, **kwargs)
|
||||
assert(isinstance(a, int)) # Curve coefficent A
|
||||
assert(isinstance(b, int)) # Curve coefficent B
|
||||
self._a = FieldElement(a, p)
|
||||
self._b = FieldElement(b, p)
|
||||
self._name = kwargs.get("name")
|
||||
|
||||
# Check that the curve is not singular
|
||||
assert((4 * (self.a ** 3)) + (27 * (self.b ** 2)) != 0)
|
||||
|
||||
if self._G is not None:
|
||||
# Check that the generator G is on the curve
|
||||
assert(self._G.oncurve())
|
||||
|
||||
if self.n is not None:
|
||||
# Check that the generator G is of curve order if a order was
|
||||
# passed as well
|
||||
assert((self.n * self.G).is_neutral)
|
||||
|
||||
@classmethod
|
||||
def init_rawcurve(cls, a, b, p):
|
||||
"""Returns a raw curve which has an undiscovered amount of points
|
||||
#E(F_p) (i.e. the domain parameters n and h are not set). This function
|
||||
can be used to create a curve which is later completed by counting
|
||||
#E(F_p) using Schoof's algorithm."""
|
||||
return cls(a = a, b = b, p = p, n = None, h = None, Gx = None, Gy = None)
|
||||
|
||||
@property
|
||||
def is_anomalous(self):
|
||||
"""Returns if the curve is anomalous, i.e. if #F(p) == p. If this is
|
||||
the case then there is an efficient method to solve the ECDLP.
|
||||
Therefore the curve is not suitable for cryptographic use."""
|
||||
return self.jinv in [ 0, 1728 ]
|
||||
|
||||
@property
|
||||
@doc_inherit(EllipticCurve)
|
||||
def domainparams(self):
|
||||
return _ShortWeierstrassCurveDomainParameters(curvetype = self.curvetype, a = self.a, b = self.b, p = self.p, n = self.n, h = self.h, G = self.G)
|
||||
|
||||
@property
|
||||
@doc_inherit(EllipticCurve)
|
||||
def curvetype(self):
|
||||
return "shortweierstrass"
|
||||
|
||||
@property
|
||||
def is_koblitz(self):
|
||||
"""Returns whether the curve allows for efficient computation of a map
|
||||
\phi in the field (i.e. that the curve is commonly known as a 'Koblitz
|
||||
Curve'). This corresponds to examples 3 and 4 of the paper "Faster
|
||||
Point Multiplication on Elliptic Curves with Efficient Endomorphisms"
|
||||
by Gallant, Lambert and Vanstone."""
|
||||
return ((self.b == 0) and ((self.p % 4) == 1)) or ((self.a == 0) and ((self.p % 3) == 1))
|
||||
|
||||
@property
|
||||
def security_bit_estimate(self):
|
||||
"""Returns the bit security estimate of the curve. Subtracts four bits
|
||||
security margin for Koblitz curves."""
|
||||
security_bits = self.n.bit_length() // 2
|
||||
if self.is_koblitz:
|
||||
security_bits -= 4
|
||||
return security_bits
|
||||
|
||||
@property
|
||||
@doc_inherit(EllipticCurve)
|
||||
def prettyname(self):
|
||||
name = [ ]
|
||||
name.append(self.pretty_name)
|
||||
if self.is_koblitz:
|
||||
name.append("(Koblitz)")
|
||||
return " ".join(name)
|
||||
|
||||
@property
|
||||
def a(self):
|
||||
"""Returns the coefficient a of the curve equation y^2 = x^3 + ax + b."""
|
||||
return self._a
|
||||
|
||||
@property
|
||||
def b(self):
|
||||
"""Returns the coefficient b of the curve equation y^2 = x^3 + ax + b."""
|
||||
return self._b
|
||||
|
||||
@property
|
||||
def jinv(self):
|
||||
"""Returns the j-invariant of the curve, i.e. 1728 * 4 * a^3 / (4 * a^3
|
||||
+ 27 * b^2)."""
|
||||
return 1728 * (4 * self.a ** 3) // ((4 * self.a ** 3) + (27 * self.b ** 2))
|
||||
|
||||
def getpointwithx(self, x):
|
||||
"""Returns a tuple of two points which fulfill the curve equation or
|
||||
None if not such points exist."""
|
||||
assert(isinstance(x, int))
|
||||
yy = ((FieldElement(x, self._p) ** 3) + (self._a * x) + self._b)
|
||||
y = yy.sqrt()
|
||||
if y:
|
||||
return (AffineCurvePoint(x, int(y[0]), self), AffineCurvePoint(x, int(y[1]), self))
|
||||
else:
|
||||
return None
|
||||
|
||||
@doc_inherit(EllipticCurve)
|
||||
def oncurve(self, P):
|
||||
return P.is_neutral or ((P.y ** 2) == (P.x ** 3) + (self.a * P.x) + self.b)
|
||||
|
||||
@doc_inherit(EllipticCurve)
|
||||
def point_conjugate(self, P):
|
||||
return AffineCurvePoint(int(P.x), int(-P.y), self)
|
||||
|
||||
@doc_inherit(EllipticCurve)
|
||||
def point_addition(self, P, Q):
|
||||
if P.is_neutral:
|
||||
# P is at infinity, O + Q = Q
|
||||
result = Q
|
||||
elif Q.is_neutral:
|
||||
# Q is at infinity, P + O = P
|
||||
result = P
|
||||
elif P == -Q:
|
||||
# P == -Q, return O (point at infinity)
|
||||
result = self.neutral()
|
||||
elif P == Q:
|
||||
# P == Q, point doubling
|
||||
s = ((3 * P.x ** 2) + self.a) // (2 * P.y)
|
||||
newx = s * s - (2 * P.x)
|
||||
newy = s * (P.x - newx) - P.y
|
||||
result = AffineCurvePoint(int(newx), int(newy), self)
|
||||
else:
|
||||
# P != Q, point addition
|
||||
s = (P.y - Q.y) // (P.x - Q.x)
|
||||
newx = (s ** 2) - P.x - Q.x
|
||||
newy = s * (P.x - newx) - P.y
|
||||
result = AffineCurvePoint(int(newx), int(newy), self)
|
||||
return result
|
||||
|
||||
@doc_inherit(EllipticCurve)
|
||||
def compress(self, P):
|
||||
return (int(P.x), int(P.y) % 2)
|
||||
|
||||
@doc_inherit(EllipticCurve)
|
||||
def uncompress(self, compressed):
|
||||
(x, ybit) = compressed
|
||||
x = FieldElement(x, self.p)
|
||||
alpha = (x ** 3) + (self.a * x) + self.b
|
||||
(beta1, beta2) = alpha.sqrt()
|
||||
if (int(beta1) % 2) == ybit:
|
||||
y = beta1
|
||||
else:
|
||||
y = beta2
|
||||
return AffineCurvePoint(int(x), int(y), self)
|
||||
|
||||
@doc_inherit(EllipticCurve)
|
||||
def enumerate_points(self):
|
||||
yield self.neutral()
|
||||
for x in range(self.p):
|
||||
points = self.getpointwithx(x)
|
||||
if points is not None:
|
||||
yield points[0]
|
||||
yield points[1]
|
||||
|
||||
def __str__(self):
|
||||
if self.hasname:
|
||||
return "ShortWeierstrassCurve<%s>" % (self.name)
|
||||
else:
|
||||
return "ShortWeierstrassCurve<y^2 = x^3 + 0x%x x + 0x%x mod 0x%x>" % (int(self.a), int(self.b), int(self.p))
|
69
toyecc/Singleton.py
Normal file
69
toyecc/Singleton.py
Normal file
@ -0,0 +1,69 @@
|
||||
#!/usr/bin/python3
|
||||
#
|
||||
# Singleton - Singleton decorator, taken from PEP318
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of jpycommon.
|
||||
#
|
||||
# jpycommon is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# jpycommon is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with jpycommon; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
# File UUID a24a2230-7bd4-4737-98ab-8aae62d1bd57
|
||||
|
||||
def singleton(cls):
|
||||
class InnerClass(cls):
|
||||
_instance = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if InnerClass._instance is None:
|
||||
InnerClass._instance = super(InnerClass, cls).__new__(cls, *args, **kwargs)
|
||||
InnerClass._instance._initialized = False
|
||||
return InnerClass._instance
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if self._initialized:
|
||||
return
|
||||
super(InnerClass, self).__init__(*args, **kwargs)
|
||||
self._initialized = True
|
||||
|
||||
InnerClass.__name__ = cls.__name__
|
||||
return InnerClass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("start")
|
||||
|
||||
@singleton
|
||||
class FooSingleton():
|
||||
_barkoo = -1
|
||||
|
||||
def __init__(self):
|
||||
print("init called")
|
||||
|
||||
def getid(self):
|
||||
return id(self) * FooSingleton._barkoo
|
||||
|
||||
print("pre init")
|
||||
x = FooSingleton()
|
||||
print(x, x.getid())
|
||||
|
||||
y = FooSingleton()
|
||||
print(y, y.getid())
|
||||
|
||||
z = FooSingleton()
|
||||
print(z, z.getid())
|
||||
|
||||
assert(x is y)
|
98
toyecc/Tools.py
Normal file
98
toyecc/Tools.py
Normal file
@ -0,0 +1,98 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
import hashlib
|
||||
import base64
|
||||
import inspect
|
||||
|
||||
def bytestoint_le(data):
|
||||
"""Converts given bytes to a little-endian integer value."""
|
||||
return sum(value << (8 * index) for (index, value) in enumerate(data))
|
||||
|
||||
def inttobytes_le(value, length):
|
||||
"""Converts a little-endian integer value into a bytes object."""
|
||||
return bytes((value >> (8 * i)) & 0xff for i in range(length))
|
||||
|
||||
def bytestoint(data):
|
||||
"""Converts given bytes to a big-endian integer value."""
|
||||
return bytestoint_le(reversed(data))
|
||||
|
||||
def inttobytes(value, length):
|
||||
"""Converts a big-endian integer value into a bytes object."""
|
||||
return bytes((value >> (8 * i)) & 0xff for i in reversed(range(length)))
|
||||
|
||||
def bits_to_bytes(bitarray):
|
||||
"""Converts a tuple of bits (e.g. a ASN.1 BitString) to a bytes object.
|
||||
Only works when number of bits is a multiple of 8."""
|
||||
|
||||
def bit_word_to_value(word):
|
||||
assert(len(word) == 8)
|
||||
return sum(value << i for (i, value) in enumerate(reversed(word)))
|
||||
|
||||
assert((len(bitarray) % 8) == 0)
|
||||
return bytes(bit_word_to_value(bitarray[i : i + 8]) for i in range(0, len(bitarray), 8))
|
||||
|
||||
def ecdsa_msgdigest_to_int(message_digest, curveorder):
|
||||
"""Performs truncation of a message digest to the bitlength of the curve
|
||||
order."""
|
||||
# Convert message digest to integer value
|
||||
e = bytestoint(message_digest)
|
||||
|
||||
# Truncate hash value if necessary
|
||||
msg_digest_bits = 8 * len(message_digest)
|
||||
if msg_digest_bits > curveorder.bit_length():
|
||||
shift = msg_digest_bits - curveorder.bit_length()
|
||||
e >>= shift
|
||||
|
||||
return e
|
||||
|
||||
def load_pem_data(filename, specifier):
|
||||
"""Loads the PEM payload, designated with a BEGIN and END specifier, from a
|
||||
file given by its filename."""
|
||||
data = None
|
||||
with open(filename, "r") as f:
|
||||
spec_begin = "-----BEGIN " + specifier + "-----"
|
||||
spec_end = "-----END " + specifier + "-----"
|
||||
for line in f:
|
||||
line = line.rstrip()
|
||||
if (data is None) and (line == spec_begin):
|
||||
data = [ ]
|
||||
elif (data is not None) and (line == spec_end):
|
||||
break
|
||||
elif data is not None:
|
||||
data.append(line)
|
||||
if data is None:
|
||||
raise Exception("Trying to parse PEM file with specifier '%s', but no such block in file found." % (specifier))
|
||||
data = base64.b64decode("".join(data).encode("utf-8"))
|
||||
return data
|
||||
|
||||
def is_power_of_two(value):
|
||||
"""Returns True if the given value is a positive power of two, False
|
||||
otherwise."""
|
||||
while value > 0:
|
||||
if value == 1:
|
||||
return True
|
||||
elif (value & 1) == 1:
|
||||
return False
|
||||
value >>= 1
|
||||
return False
|
171
toyecc/TwistedEdwardsCurve.py
Normal file
171
toyecc/TwistedEdwardsCurve.py
Normal file
@ -0,0 +1,171 @@
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
import collections
|
||||
from .FieldElement import FieldElement
|
||||
from .AffineCurvePoint import AffineCurvePoint
|
||||
from .EllipticCurve import EllipticCurve
|
||||
from .DocInherit import doc_inherit
|
||||
import toyecc.MontgomeryCurve
|
||||
|
||||
_TwistedEdwardsCurveDomainParameters = collections.namedtuple("TwistedEdwardsCurveDomainParameters", [ "curvetype", "a", "d", "p", "n", "G" ])
|
||||
|
||||
class TwistedEdwardsCurve(EllipticCurve):
|
||||
"""Represents an elliptic curve over a finite field F_P that satisfies the
|
||||
Twisted Edwards equation a x^2 + y^2 = 1 + d x^2 y^2."""
|
||||
pretty_name = "Twisted Edwards"
|
||||
|
||||
def __init__(self, a, d, p, n, h, Gx, Gy, **kwargs):
|
||||
"""Create an elliptic Twisted Edwards curve given the equation
|
||||
coefficients a and d, the curve field's modulus p, the order of the
|
||||
curve n and the generator point G's X and Y coordinates in affine
|
||||
representation, Gx and Gy."""
|
||||
EllipticCurve.__init__(self, p, n, h, Gx, Gy, **kwargs)
|
||||
assert(isinstance(a, int)) # Curve coefficent A
|
||||
assert(isinstance(d, int)) # Curve coefficent D
|
||||
self._a = FieldElement(a, p)
|
||||
self._d = FieldElement(d, p)
|
||||
self._name = kwargs.get("name")
|
||||
|
||||
# Check that the curve is not singular
|
||||
assert(self.d * (1 - self.d) != 0)
|
||||
|
||||
if self._G is not None:
|
||||
# Check that the generator G is on the curve
|
||||
assert(self._G.oncurve())
|
||||
|
||||
# Check that the generator G is of curve order
|
||||
assert((self.n * self.G).is_neutral)
|
||||
|
||||
@property
|
||||
@doc_inherit(EllipticCurve)
|
||||
def domainparams(self):
|
||||
return _TwistedEdwardsCurveDomainParameters(curvetype = self.curvetype, a = self.a, d = self.d, p = self.p, n = self.n, G = self.G)
|
||||
|
||||
@property
|
||||
@doc_inherit(EllipticCurve)
|
||||
def curvetype(self):
|
||||
return "twistededwards"
|
||||
|
||||
@property
|
||||
def a(self):
|
||||
"""Returns the coefficient a of the curve equation a x^2 + y^2 = 1 +
|
||||
d x^2 y^2."""
|
||||
return self._a
|
||||
|
||||
@property
|
||||
def d(self):
|
||||
"""Returns the coefficient d of the curve equation a x^2 + y^2 = 1 +
|
||||
d x^2 y^2."""
|
||||
return self._d
|
||||
|
||||
@property
|
||||
def B(self):
|
||||
"""Returns the length of the curve's field modulus in bits plus one."""
|
||||
return self._p.bit_length() + 1
|
||||
|
||||
@property
|
||||
def is_complete(self):
|
||||
"""Returns if the twisted Edwards curve is complete. This is the case
|
||||
exactly when d is a quadratic non-residue modulo p."""
|
||||
return self.d.is_qnr
|
||||
|
||||
@doc_inherit(EllipticCurve)
|
||||
def neutral(self):
|
||||
return AffineCurvePoint(0, 1, self)
|
||||
|
||||
@doc_inherit(EllipticCurve)
|
||||
def is_neutral(self, P):
|
||||
return (P.x == 0) and (P.y == 1)
|
||||
|
||||
@doc_inherit(EllipticCurve)
|
||||
def oncurve(self, P):
|
||||
return (self.a * P.x ** 2) + P.y ** 2 == 1 + self.d * P.x ** 2 * P.y ** 2
|
||||
|
||||
@doc_inherit(EllipticCurve)
|
||||
def point_conjugate(self, P):
|
||||
return AffineCurvePoint(int(-P.x), int(P.y), self)
|
||||
|
||||
@doc_inherit(EllipticCurve)
|
||||
def point_addition(self, P, Q):
|
||||
x = (P.x * Q.y + Q.x * P.y) // (1 + self.d * P.x * Q.x * P.y * Q.y)
|
||||
y = (P.y * Q.y - self.a * P.x * Q.x) // (1 - self.d * P.x * Q.x * P.y * Q.y)
|
||||
return AffineCurvePoint(int(x), int(y), self)
|
||||
|
||||
def to_montgomery(self, b = None):
|
||||
"""Converts the twisted Edwards curve domain parameters to Montgomery
|
||||
domain parameters. For this conversion, b can be chosen semi-freely.
|
||||
If the native b coefficient is a quadratic residue modulo p, then the
|
||||
freely chosen b value must also be. If it is a quadratic non-residue,
|
||||
then so must be the surrogate b coefficient. If b is omitted, the
|
||||
native b value is used. The generator point of the twisted Edwards
|
||||
curve is also converted to Montgomery form. For this conversion,
|
||||
there's an invariant (one of two possible outcomes). An arbitrary
|
||||
bijection is used for this."""
|
||||
assert((b is None) or isinstance(b, int))
|
||||
|
||||
# Calculate the native montgomery coefficents a, b first
|
||||
a = 2 * (self.a + self.d) // (self.a - self.d)
|
||||
native_b = 4 // (self.a - self.d)
|
||||
if b is None:
|
||||
b = native_b
|
||||
else:
|
||||
# If a b value was supplied, make sure is is either a QR or QNR mod
|
||||
# p, depending on what the native b value was
|
||||
b = FieldElement(b, self.p)
|
||||
if native_b.is_qr != b.is_qr:
|
||||
raise Exception("The b coefficient of the resulting curve must be a quadratic %s modulo p, %s is not." % ([ "non-residue", "residue" ][native_b.is_qr], str(b)))
|
||||
|
||||
# Generate the raw curve without a generator yet
|
||||
raw_curve = toyecc.MontgomeryCurve.MontgomeryCurve(
|
||||
a = int(a),
|
||||
b = int(b),
|
||||
p = self.p,
|
||||
n = self.n,
|
||||
h = self.h,
|
||||
Gx = None,
|
||||
Gy = None,
|
||||
)
|
||||
|
||||
# Then convert the original generator point using the raw curve to
|
||||
# yield a birationally equivalent generator point
|
||||
G_m = self.G.convert(raw_curve)
|
||||
|
||||
# And create the curve again, setting this generator
|
||||
montgomery_curve = toyecc.MontgomeryCurve.MontgomeryCurve(
|
||||
a = int(a),
|
||||
b = int(b),
|
||||
p = self.p,
|
||||
n = self.n,
|
||||
h = self.h,
|
||||
Gx = int(G_m.x),
|
||||
Gy = int(G_m.y),
|
||||
)
|
||||
|
||||
return montgomery_curve
|
||||
|
||||
def __str__(self):
|
||||
if self.hasname:
|
||||
return "TwistedEdwardsCurve<%s>" % (self.name)
|
||||
else:
|
||||
return "TwistedEdwardsCurve<0x%x x^2 + y^2 = 1 + 0x%x x^2 y^2 mod 0x%x>" % (int(self.a), int(self.d), int(self.p))
|
95
toyecc/__init__.py
Normal file
95
toyecc/__init__.py
Normal file
@ -0,0 +1,95 @@
|
||||
"""toyecc - Elliptic Curve Cryptography demonstration library
|
||||
|
||||
toyecc is a library that is supposed to demonstrate and teach how Elliptic
|
||||
Curve Cryptography (ECC) works. It is implemented in pure Python and neither
|
||||
aims to be feature-complete not side-channel resistant nor secure in any way.
|
||||
Please keep this in mind when using toyecc. Certain algorithms have been
|
||||
deliberately implemented exactly without precautions against side-channel
|
||||
attacks in order to cleanly demonstrate the concepts.
|
||||
|
||||
There is a curve database included in toyecc which already knows lots of
|
||||
interesting elliptic curves by name:
|
||||
|
||||
import toyecc
|
||||
curve = toyecc.getcurvebyname("secp112r1")
|
||||
print(curve)
|
||||
ShortWeierstrassCurve<secp112r1>
|
||||
|
||||
On this curve you can now create a public/private keypair:
|
||||
|
||||
privkey = toyecc.ECPrivateKey.generate(curve)
|
||||
print(privkey)
|
||||
PrivateKey<d = 0x89fb9821aa5154c9934b3e0268ef>
|
||||
print(privkey.pubkey)
|
||||
PublicKey<(0x69976db41f5e487928463b9f8a38, 0xda1fdba3de89c58683bd2d635430)>
|
||||
|
||||
You can also regenerate this keypair later on if you wish to remain it static
|
||||
and not change with every invocation:
|
||||
|
||||
privkey = toyecc.ECPrivateKey(0x89fb9821aa5154c9934b3e0268ef, curve)
|
||||
print(privkey)
|
||||
PrivateKey<d = 0x89fb9821aa5154c9934b3e0268ef>
|
||||
|
||||
You can use this keypair to perform actions like ECDSA signing:
|
||||
|
||||
signature = privkey.ecdsa_sign(b"Foobar", "sha1")
|
||||
print(signature)
|
||||
ECDSASignature(hashalg='sha1', r=1762251013383878369191057972852867, s=3691758261134002001156831324480002)
|
||||
|
||||
If you want to recreate a public key anew, you can also do so by first creating
|
||||
the public point:
|
||||
|
||||
pubkeypt = toyecc.AffineCurvePoint(0x69976db41f5e487928463b9f8a38, 0xda1fdba3de89c58683bd2d635430, curve)
|
||||
print(pubkeypt)
|
||||
(0x69976db41f5e487928463b9f8a38, 0xda1fdba3de89c58683bd2d635430)
|
||||
|
||||
Then you can just wrap the point in a ECPublicKey object to have access to
|
||||
methods like ECDSA verification and such:
|
||||
|
||||
pubkey = toyecc.ECPublicKey(pubkeypt)
|
||||
print(pubkey)
|
||||
PublicKey<(0x69976db41f5e487928463b9f8a38, 0xda1fdba3de89c58683bd2d635430)>
|
||||
|
||||
Lastly, you can verify the signature you created earlier:
|
||||
|
||||
pubkey.ecdsa_verify(b"Foobar", signature)
|
||||
True
|
||||
|
||||
And change the message so the signature would become invalid:
|
||||
|
||||
pubkey.ecdsa_verify(b"Barfoo", signature)
|
||||
False
|
||||
"""
|
||||
|
||||
#
|
||||
# toyecc - A small Elliptic Curve Cryptography Demonstration.
|
||||
# Copyright (C) 2011-2022 Johannes Bauer
|
||||
#
|
||||
# This file is part of toyecc.
|
||||
#
|
||||
# toyecc is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; this program is ONLY licensed under
|
||||
# version 3 of the License, later versions are explicitly excluded.
|
||||
#
|
||||
# toyecc is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with toyecc; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
# Johannes Bauer <JohannesBauer@gmx.de>
|
||||
#
|
||||
|
||||
from .FieldElement import FieldElement
|
||||
from .AffineCurvePoint import AffineCurvePoint
|
||||
from .CurveDB import getcurvedb, getcurveentry, getcurvebyname, getcurvenames
|
||||
from .ECPrivateKey import ECPrivateKey
|
||||
from .ECPublicKey import ECPublicKey
|
||||
from .ShortWeierstrassCurve import ShortWeierstrassCurve
|
||||
from .CRT import CRT
|
||||
|
||||
VERSION = "0.0.9rc0"
|
Loading…
Reference in New Issue
Block a user