ECDSA r, s encoding as a signature
this link doesnt seem to add up, any suggestions?
Bitcoin Stack Exchange is a question and answer site for Bitcoin users, developers, and enthusiasts. It only takes a minute to sign up.
Sign up to join this communityThe structure of a DER encoded ECDSA signature is as follows:
30
identifies a SEQUENCE
in ASN1 encoding, which is followed by the length of z
(the sequence). r
and s
can be either 32 or 33 bytes long, depending on how big the DER encoded values are. r
and s
are always leaded by 02
, which identify an integer value in ASN1. Finally, the tailing (ht
) byte represents the hashtype
In order to parse a signature to extract both values you should check their length, which is always 1 byte long, and extract the value depending on the result. Here's the code to do so:
def parse_element(hex_str, offset, element_size):
"""
:param hex_str: string to parse the element from.
:type hex_str: hex str
:param offset: initial position of the object inside the hex_str.
:type offset: int
:param element_size: size of the element to extract.
:type element_size: int
:return: The extracted element from the provided string, and the updated offset after extracting it.
:rtype tuple(str, int)
"""
return hex_str[offset:offset+element_size], offset+element_size
def dissect_signature(hex_sig):
"""
Extracts the r, s and ht components from a Bitcoin ECDSA signature.
:param hex_sig: Signature in hex format.
:type hex_sig: hex str
:return: r, s, t as a tuple.
:rtype: tuple(str, str, str)
"""
offset = 0
# Check the sig contains at least the size and sequence marker
assert len(hex_sig) > 4, "Wrong signature format."
sequence, offset = parse_element(hex_sig, offset, 2)
# Check sequence marker is correct
assert sequence == '30', "Wrong sequence marker."
signature_length, offset = parse_element(hex_sig, offset, 2)
# Check the length of the remaining part matches the length of the signature + the length of the hashflag (1 byte)
assert len(hex_sig[offset:])/2 == int(signature_length, 16) + 1, "Wrong length."
# Get r
marker, offset = parse_element(hex_sig, offset, 2)
assert marker == '02', "Wrong r marker."
len_r, offset = parse_element(hex_sig, offset, 2)
len_r_int = int(len_r, 16) * 2 # Each byte represents 2 characters
r, offset = parse_element(hex_sig, offset, len_r_int)
# Get s
marker, offset = parse_element(hex_sig, offset, 2)
assert marker == '02', "Wrong s marker."
len_s, offset = parse_element(hex_sig, offset, 2)
len_s_int = int(len_s, 16) * 2 # Each byte represents 2 characters
s, offset = parse_element(hex_sig, offset, len_s_int)
# Get ht
ht, offset = parse_element(hex_sig, offset, 2)
assert offset == len(hex_sig), "Wrong parsing."
return r, s, ht
example_sig = None # insert_sig_here
r, s, ht = dissect_signature(example_sig)
print "r: %s\ns: %s\nht: %s\n" % (r, s, ht)
Example: https://blockchain.info/tx/463b93f613da09b51d692f48674137f303050e4a5f2cc60ebf349b3c45e192d6
First "Signature" (sans the public key) which contains the R and S values is:
304402206878b5690514437a2342405029426cc2b25b4a03fc396fef845d656cf62bad2c022018610a8d37e3384245176ab49ddbdbe8da4133f661bf5ea7ad4e3d2b912d856f01
This breaks down into:
30
44
02
20
6878b5690514437a2342405029426cc2b25b4a03fc396fef845d656cf62bad2c
02
20
18610a8d37e3384245176ab49ddbdbe8da4133f661bf5ea7ad4e3d2b912d856f
01
The 30
, I think, is standard.
The 44
is the number of bytes that will be taken up by, R, S, and their preceding numbers. 44 in hex is 68 in decimal form. Thus referencing that after this point until (but not including) the 01
there will be 68 bytes of data or 136 alpha numeric characters.
The 02
is a number which identifies the following value (in this case, R) as an integer (I think)
The 20
is the number which identifies the number of bytes (or number of hex characters/numbers times 2) of the following value (in this case, R). This value is in hex (ie: 20 in hex is 32 in decimal, signifying 32 bytes or 64 alpha/numeric characters)
The 6878b5690514437a2342405029426cc2b25b4a03fc396fef845d656cf62bad2c
is the actual "R" value referenced by the preceding 0220
.
The second 02
identifies the following value as an integer (in this case, S).
The second 20
is the number which identifies the number of bytes (or number of hex characters/numbers times 2) of the following value (in this case, S). This value is in hex (ie: 20 in hex is 32 in decimal, signifying 32 bytes or 64 alpha/numeric characters)
The 18610a8d37e3384245176ab49ddbdbe8da4133f661bf5ea7ad4e3d2b912d856f
is the "S" value referenced by the preceding 0220
.
I think the 01
is merely a reference as to the end of this part of the transaction data.
Some R and S values may be different lengths, the preceding numbers will tell you how long they are.
Information for additional reference:
R = 47253809947851177065887724633329625063088643784040492056218945870752194997548
S = 11026965355983493404719379810734327200902731292741433431270495068542334764399
The Second Input in this Transaction
304402205473f7d4c042bcada7ab63d3d9997bb7945353d388abbea2435d6536c84dca9b0220635f3c3660c375f34a8101b4040b8e046749e8b8517439b090b34bd14b41388301
Breaks down to the following
30
44
02
20
5473f7d4c042bcada7ab63d3d9997bb7945353d388abbea2435d6536c84dca9b
02
20
635f3c3660c375f34a8101b4040b8e046749e8b8517439b090b34bd14b413883
01
To reference your comment in your question
In the first part of the example transaction I showed, to rip the R value from the data, you would have to remove the first 3 bytes (or the 304402
, aka the first 6 individual numbers). Then you would have to convert the subsequent first byte (or 2 digit number) being 20
. You would then have to convert this into decimal (which would be 32). Then erase the 20
and then read ONLY the next 32 bytes (aka the "R" value), which could also be described as reading the next 64 alphanumeric characters. You could then start the whole thing over again and shift the values over to identify the S value instead of the R value.