I am using the python ecdsa
library for signing messages for blockchain transactions. In the blockchain specification it says, for secp256r1
that the signature should have a length of 65 bytes where:
The signature must be of length 65 bytes in the form of [r, s, v] where the first 32 bytes are r, the second 32 bytes are s and the last byte is v.
and
The v represents the recovery ID, which must be normalized to 0, 1, 2 or 3. Note that unlike EIP-155 chain ID is not used to calculate the v value
I consistently get the first 64 bytes using:
sign_deterministic(data, hashfunc=hashlib.sha256)
But not sure where ecdsa holds, or I can compute, the v
byte?
I see in the source code (Rust fastcrypto) used on the blockchain it is doing:
// Compute recovery id and normalize signature
let is_r_odd = y.is_odd();
let is_s_high = sig.s().is_high();
let is_y_odd = is_r_odd ^ is_s_high;
let sig_low = sig.normalize_s().unwrap_or(sig);
let recovery_id = RecoveryId::new(is_y_odd.into(), false);
But in ecdsa
all that is hidden behind the signing function noted above.
Signing with ECDSA is explained e.g. here. In the following, the terms used in this post are applied:
The ecdsa library does not support the additional determination of the recovery ID when creating the signature. However, it is possible to determine the recovery ID subsequently. To do this, the
k
value used when creating the signature must first be determined, which is possible since you are using deterministic ECDSA according to RFC 6979. Herek
depends defined on the hash, the message and the private signing key.When running
sign_deterministic(data, hashfunc=hashlib.sha256)
, the following logic is executed to determinek
:The corresponding code location can be found here. Note that the
sign_deterministic(data, hashfunc=hashlib.sha256)
call uses the default value for the extra entropy (extra_entropy=b""
) and allows too large hashes to be truncated (allow_truncate=True
).With
k
,R
can be determined as the product ofk
and generator pointG
. Formally,k
can be considered as a raw private key andR
as a raw public key, so the existing functionalities for keys can be used andR
can be easily determined as follows:For known
R
, the recovery ID can be derived as follows:For an explanation of the last logic, see here (including the comments). Note that the probability for recovery IDs 2 and 3 is vanishingly small.
A signature including recovery ID can then be generated with the ecdsa library, e.g. as follows:
To verify the results and also to demonstrate that there are more convenient libraries for generating a signature with recovery ID, the following pycoin code is used, which requires only the library function function
sign_with_recid()
, which returns a tuple withr
,s
and the recovery ID. By default, this method internally uses thek
derivation from RFC6979 (i.e. the logic also applied in the ecdsa example):When using the same signing key, the same message and the same digest, the values are identical.
The current question is about deterministic ECDSA. But even for non-deterministic ECDSA, the ecdsa library allows the recovery ID to be determined (and much more easily than in the case of deterministic ECDSA). Since the random
k
used in signing cannot be accessed, the generation ofk
is simply moved outside and then the signature is generated with thisk
(which is supported, seesign()
):For known
k
,R
and finally the recovery ID can be determined as above.