| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- # Non-recoverable signatures are encoded using a DER sequence of two integers
- # We locally implement serialization and deserialization for this specific spec
- # with constrained inputs.
- # This is done locally to avoid importing a 3rd-party library, in this very sensitive
- # project. asn1tools and pyasn1 were used as reference APIs, see how in
- # tests/core/test_utils_asn1.py
- #
- # See more about DER encodings, and ASN.1 in general, here:
- # http://luca.ntop.org/Teaching/Appunti/asn1.html
- #
- # These methods are NOT intended for external use outside of this project. They do not
- # fully validate inputs and make assumptions that are not *generally* true.
- from typing import (
- Iterator,
- Tuple,
- )
- from eth_utils import (
- apply_to_return_value,
- big_endian_to_int,
- int_to_big_endian,
- )
- @apply_to_return_value(bytes)
- def two_int_sequence_encoder(signature_r: int, signature_s: int) -> Iterator[int]:
- """
- Encode two integers using DER, defined as:
- ::
- ECDSASpec DEFINITIONS ::= BEGIN
- ECDSASignature ::= SEQUENCE {
- r INTEGER,
- s INTEGER
- }
- END
- Only a subset of integers are supported: positive, 32-byte ints.
- See: https://docs.microsoft.com/en-us/windows/desktop/seccertenroll/about-sequence
- """
- # Sequence tag
- yield 0x30
- encoded1 = _encode_int(signature_r)
- encoded2 = _encode_int(signature_s)
- # Sequence length
- yield len(encoded1) + len(encoded2)
- yield from encoded1
- yield from encoded2
- def two_int_sequence_decoder(encoded: bytes) -> Tuple[int, int]:
- """
- Decode bytes to two integers using DER, defined as:
- ::
- ECDSASpec DEFINITIONS ::= BEGIN
- ECDSASignature ::= SEQUENCE {
- r INTEGER,
- s INTEGER
- }
- END
- Only a subset of integers are supported: positive, 32-byte ints.
- r is returned first, and s is returned second
- See: https://docs.microsoft.com/en-us/windows/desktop/seccertenroll/about-sequence
- """
- if encoded[0] != 0x30:
- raise ValueError(
- f"Encoded sequence must start with 0x30 byte, but got {encoded[0]}"
- )
- # skip sequence length
- int1, rest = _decode_int(encoded[2:])
- int2, empty = _decode_int(rest)
- if len(empty) != 0:
- raise ValueError(
- "Encoded sequence must not contain any trailing data, but had "
- f"{repr(empty)}"
- )
- return int1, int2
- @apply_to_return_value(bytes)
- def _encode_int(primitive: int) -> Iterator[int]:
- # See: https://docs.microsoft.com/en-us/windows/desktop/seccertenroll/about-integer
- # Integer tag
- yield 0x02
- encoded = int_to_big_endian(primitive)
- if encoded[0] >= 128:
- # Indicate that integer is positive
- # (it always is, but doesn't always need the flag)
- yield len(encoded) + 1
- yield 0x00
- else:
- yield len(encoded)
- yield from encoded
- def _decode_int(encoded: bytes) -> Tuple[int, bytes]:
- # See: https://docs.microsoft.com/en-us/windows/desktop/seccertenroll/about-integer
- if encoded[0] != 0x02:
- raise ValueError(
- "Encoded value must be an integer, starting with on 0x02 byte, but got "
- f"{encoded[0]}"
- )
- length = encoded[1]
- # to_int can handle leading zeros
- decoded_int = big_endian_to_int(encoded[2 : 2 + length])
- return decoded_int, encoded[2 + length :]
|