Skip to content

Commit efde46d

Browse files
committed
feat: add DER encoding methods and update key type alias in key_utils
Signed-off-by: MonaaEid <monaa_eid@hotmail.com>
1 parent 6bde53c commit efde46d

1 file changed

Lines changed: 45 additions & 41 deletions

File tree

src/hiero_sdk_python/crypto/public_key.py

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -434,41 +434,43 @@ def to_bytes_der(self) -> bytes:
434434
format=serialization.PublicFormat.SubjectPublicKeyInfo
435435
)
436436

437-
@staticmethod
438-
def _encode_der_length(length: int) -> bytes:
439-
"""Encode a DER length field."""
440-
if length < 0:
441-
raise ValueError("DER length must be non-negative")
442-
if length < 0x80:
443-
return bytes([length])
444-
445-
length_bytes = length.to_bytes((length.bit_length() + 7) // 8, "big")
446-
return bytes([0x80 | len(length_bytes)]) + length_bytes
447-
448-
@staticmethod
449-
def _encode_der_oid_component(value: int) -> bytes:
450-
"""Encode one OID component in base-128 continuation format."""
437+
@classmethod
438+
def _encode_vlq(cls, value: int) -> bytes:
439+
"""
440+
Encode a value in Variable-Length Quantity (VLQ) format.
441+
"""
451442
if value < 0:
452-
raise ValueError("OID components must be non-negative")
443+
raise ValueError("VLQ value must be non-negative")
453444
if value == 0:
454445
return b"\x00"
455446

456-
base128 = []
447+
encoded = bytearray()
457448
while value > 0:
458-
base128.append(value & 0x7F)
449+
encoded.insert(0, value & 0x7F)
459450
value >>= 7
460451

461-
encoded = bytearray()
462-
for i in range(len(base128) - 1, -1, -1):
463-
byte = base128[i]
464-
if i != 0:
465-
byte |= 0x80
466-
encoded.append(byte)
452+
# Set the high bit (0x80) on all bytes except the last
453+
for i in range(len(encoded) - 1):
454+
encoded[i] |= 0x80
455+
467456
return bytes(encoded)
468457

469-
@staticmethod
470-
def _encode_der_oid(oid: str) -> bytes:
471-
"""Encode a dotted OID string into DER OID bytes including tag and length."""
458+
@classmethod
459+
def _encode_der_length(cls, length: int) -> bytes:
460+
"""Encode a DER length field per X.690 standards."""
461+
if length < 0:
462+
raise ValueError("DER length must be non-negative")
463+
if length < 0x80:
464+
return bytes([length])
465+
466+
length_bytes = length.to_bytes((length.bit_length() + 7) // 8, "big")
467+
return bytes([0x80 | len(length_bytes)]) + length_bytes
468+
469+
@classmethod
470+
def _encode_der_oid(cls, oid: str) -> bytes:
471+
"""
472+
Encode a dotted OID string into DER OID bytes including tag and length.
473+
"""
472474
parts = [int(part) for part in oid.split(".")]
473475
if len(parts) < 2:
474476
raise ValueError("OID must contain at least two components")
@@ -477,30 +479,33 @@ def _encode_der_oid(oid: str) -> bytes:
477479
if first not in (0, 1, 2):
478480
raise ValueError("Invalid OID first component")
479481
if second < 0 or (first < 2 and second > 39):
480-
raise ValueError("Invalid OID second component")
482+
if not (first == 2):
483+
raise ValueError("Invalid OID second component")
481484

482-
encoded = bytearray([40 * first + second])
485+
# Encode the combined root
486+
encoded = bytearray(cls._encode_vlq(40 * first + second))
483487

484488
for value in parts[2:]:
485-
encoded.extend(PublicKey._encode_der_oid_component(value))
489+
encoded.extend(cls._encode_vlq(value))
486490

487-
return bytes([0x06]) + PublicKey._encode_der_length(len(encoded)) + bytes(encoded)
491+
return bytes([0x06]) + cls._encode_der_length(len(encoded)) + bytes(encoded)
488492

489-
@staticmethod
490-
def _encode_der_sequence(content: bytes) -> bytes:
491-
"""Encode DER SEQUENCE for content bytes."""
492-
return bytes([0x30]) + PublicKey._encode_der_length(len(content)) + content
493+
@classmethod
494+
def _encode_der_sequence(cls, content: bytes) -> bytes:
495+
"""Encode DER SEQUENCE tag, length, and content per X.690 standards."""
496+
return bytes([0x30]) + cls._encode_der_length(len(content)) + content
493497

494-
@staticmethod
495-
def _encode_der_bit_string(content: bytes) -> bytes:
496-
"""Encode DER BIT STRING for content bytes."""
498+
@classmethod
499+
def _encode_der_bit_string(cls, content: bytes) -> bytes:
500+
"""
501+
Encode DER BIT STRING tag, length, and content per X.690 standards.
502+
"""
497503
payload = bytes([0x00]) + content
498-
return bytes([0x03]) + PublicKey._encode_der_length(len(payload)) + payload
504+
return bytes([0x03]) + cls._encode_der_length(len(payload)) + payload
499505

500506
def to_bytes_der_ecdsa_compressed(self) -> bytes:
501507
"""
502-
Returns DER-encoded SubjectPublicKeyInfo for secp256k1 using a compressed point.
503-
508+
Returns DER-encoded SubjectPublicKeyInfo for secp256k1 using a compressed SEC1 point.
504509
Raises:
505510
ValueError: If this key is not ECDSA secp256k1.
506511
"""
@@ -512,7 +517,6 @@ def to_bytes_der_ecdsa_compressed(self) -> bytes:
512517
self._encode_der_oid("1.2.840.10045.2.1")
513518
+ self._encode_der_oid("1.3.132.0.10")
514519
)
515-
516520
compressed_point = self.to_bytes_ecdsa(compressed=True)
517521
subject_public_key = self._encode_der_bit_string(compressed_point)
518522

0 commit comments

Comments
 (0)