crypto.ecdsa #
ecdsa
ecdsa module for V language. Its currently (expanded) to support the following curves:
- NIST P-256 curve, commonly referred as prime256v1 or secp256r1
- NIST P-384 curve, commonly referred as secp384r1
- NIST P-521 curve, commonly referred as secp521r1
- A famous Bitcoin curve, commonly referred as secp256k1
Backends
crypto.ecdsa has two independent, interchangeable backends -- one is compiled in, never both, selected by the presence of the -d use_openssl build flag:
- mbedTLS (default,
-d use_opensslabsent): uses the mbedTLS library already vendored underthirdparty/mbedtlsfor this repo's own TLS 1.3/ QUIC stack, so it needs no external dependency to build or run. This is the backend most callers get automatically. - OpenSSL (opt-in,
-d use_opensslpresent): uses the system's OpenSSL development headers/libraries. Requires OpenSSL to be installed and discoverable by the C compiler.
Both backends implement the same public API (generate_key, new_key_from_seed, PrivateKey.new, .sign, .bytes, .public_key, .derive_shared_secret, .equal, .free, PublicKey.verify, .equal, .free, .uncompressed_bytes, PublicKey.from_uncompressed_bytes), so calling code compiles unchanged regardless of which backend is selected.
One documented difference: new_key_from_seed on the mbedTLS backend rejects a seed whose big-endian value is outside [1, curve_order-1] (mbedTLS's mbedtls_ecp_read_key range-checks the scalar), while the OpenSSL backend performs no such check. Every seed that PrivateKey.bytes() produces is in range, so a key saved with either backend reloads with both.
The default mbedTLS backend does not (yet) implement everything the OpenSSL backend does -- these return a clear, informative error instead of silently behaving differently, and are only available under -d use_openssl today:
PrivateKey.sign()/sign_with_options()withhash_config: .with_custom_hash-- mbedTLS's deterministic-nonce signing (RFC 6979, compiled in by default) needs a real digest-algorithm identifier to seed its internal HMAC-DRBG, and an arbitrary caller-suppliedhash.Hashcarries no such identifier. Verifying a signature with a custom hash is unaffected (mbedTLS's own verify call takes no digest-algorithm parameter at all).pubkey_from_bytes,pubkey_from_string,privkey_from_string(PEM/DER loading) -- format-parsing work not yet ported; no caller in this repo currently needs them on the default backend.
Example
import crypto.ecdsa
fn main() {
// create default NIST P-256 secp256r1 curve key pair. If you wish to generate another curve,
// use: `pbkey, pvkey := ecdsa.generate_key(nid: .secp521r1)!` instead.
pbkey, pvkey := ecdsa.generate_key()!
message_tobe_signed := 'Hello ecdsa'.bytes()
// create a signature with the recommended hash
signature := pvkey.sign(message_tobe_signed)!
// verify the message with the signature
verified := pbkey.verify(message_tobe_signed, signature)!
dump(verified) // should be true
// free allocated keys when you have done with your work.
pbkey.free()
pvkey.free()
}
Constants #
const C.MBEDTLS_ECP_DP_SECP256R1 int
Curve/group identifiers (mbedtls_ecp_group_id, ecp.h).
const C.MBEDTLS_ECP_DP_SECP384R1 int
const C.MBEDTLS_ECP_DP_SECP521R1 int
const C.MBEDTLS_ECP_DP_SECP256K1 int // Point format (mbedtls_ecp_point_write_binary's `format` param, ecp.h).
const C.MBEDTLS_ECP_PF_UNCOMPRESSED int // Message-digest type identifiers (mbedtls_md_type_t, md.h) -- only the
// three this module's own curve->hash mapping needs.
const C.MBEDTLS_MD_SHA256 int
const C.MBEDTLS_MD_SHA384 int
const C.MBEDTLS_MD_SHA512 int // Upper bounds for stack/heap buffer sizing (ecp.h/ecdsa.h): the largest
// possible uncompressed SEC1 point (1 tag byte + 2*66 for P-521) and the
// largest possible DER Ecdsa-Sig-Value for any curve this module supports.
const C.MBEDTLS_ECP_MAX_PT_LEN int
const C.MBEDTLS_ECDSA_MAX_LEN int
fn generate_key #
fn generate_key(opt CurveOptions) !(PublicKey, PrivateKey)
generate_key generates a new key pair. If opt was not provided, its default to prime256v1 curve. If you want another curve, use pubkey, pivkey := ecdsa.generate_key(nid: .secp384r1)! instead.
fn new_key_from_seed #
fn new_key_from_seed(seed []u8, opt CurveOptions) !PrivateKey
new_key_from_seed creates a new private key from the seed bytes. If opt was not provided, its default to prime256v1 curve.
Notes on the seed:
You should make sure, the seed bytes come from a cryptographically secure random generator, likes the crypto.rand or other trusted sources. Internally, the seed size's would be checked to not exceed the key size of underlying curve, ie, 32 bytes length for p-256 and secp256k1, 48 bytes length for p-384 and 66 bytes length for p-521. Its recommended to use seed with bytes length matching with underlying curve key size.
The seed is read as a big-endian integer and must be a valid private scalar for the curve, i.e. in [1, curve_order-1] -- mbedTLS's mbedtls_ecp_read_key enforces this and any other value is rejected with an error. This is stricter than the -d use_openssl backend, which performs no such range check; the two agree on every seed that PrivateKey.bytes() can produce (always in range), so keys saved with one backend reload with the other.
fn privkey_from_string #
fn privkey_from_string(s string) !PrivateKey
privkey_from_string loads a PrivateKey from a PEM-formatted string. Not implemented for the default mbedTLS backend -- see this file's own module doc comment.
fn pubkey_from_bytes #
fn pubkey_from_bytes(bytes []u8) !PublicKey
pubkey_from_bytes loads a PublicKey from DER-encoded bytes in der. Not implemented for the default mbedTLS backend -- see this file's own module doc comment.
fn pubkey_from_string #
fn pubkey_from_string(s string) !PublicKey
pubkey_from_string loads a PublicKey from a PEM-formatted string. Not implemented for the default mbedTLS backend -- see this file's own module doc comment.
fn HashConfig.from #
fn HashConfig.from[W](input W) !HashConfig
fn KeyFlag.from #
fn KeyFlag.from[W](input W) !KeyFlag
fn Nid.from #
fn Nid.from[W](input W) !Nid
fn PrivateKey.new #
fn PrivateKey.new(opt CurveOptions) !PrivateKey
PrivateKey.new creates a new key pair. By default, it would create a prime256v1 based key. Dont forget to call .free() after finish with your key.
fn PublicKey.from_uncompressed_bytes #
fn PublicKey.from_uncompressed_bytes(bytes []u8, opt CurveOptions) !PublicKey
PublicKey.from_uncompressed_bytes reconstructs a public key (no private component) from an uncompressed EC point 0x04 || X || Y, as received on the wire in a TLS 1.3 key_share extension. opt.nid must match the curve the peer actually used (v1 callers of net.quic only ever use .prime256v1 here, since X25519 is handled entirely by crypto.x25519 instead).
enum HashConfig #
enum HashConfig {
with_recommended_hash
with_no_hash
with_custom_hash
}
HashConfig is an enumeration of the possible options for key signing (verifying).
enum Nid #
enum Nid {
prime256v1
secp384r1
secp521r1
secp256k1
}
Nid is an enumeration of the supported curves. Independent of ecdsa_d_use_openssl.v's own Nid (only one of the two files is ever compiled at once, so there's no conflict) -- this one carries no C.* value binding, since mbedTLS's curve identifiers are a different integer space (mbedtls_ecp_group_id) than OpenSSL's NIDs.
struct CurveOptions #
struct CurveOptions {
pub mut:
// default to NIST P-256 curve
nid Nid = .prime256v1
// by default, allow arbitrary size of seed bytes as key.
// Set it to `true` when you need fixed size, using the curve key size.
// Its main purposes is to support the `.new_key_from_seed` call.
fixed_size bool
}
CurveOptions represents configuration options to drive keypair generation.
struct PrivateKey #
struct PrivateKey {
// mbedtls_ecdsa_context is mbedTLS's own typedef of mbedtls_ecp_keypair --
// one opaque type covers both PrivateKey (grp+d+Q all set) and PublicKey
// (grp+Q set, d left zero), mirroring how the OpenSSL backend reuses one
// &C.EVP_PKEY for both.
ctx &C.mbedtls_ecdsa_context
mut:
// nid records which curve this key uses -- mbedTLS's raw ecp/ecdsa/ecdh
// API (unlike OpenSSL's self-describing EVP_PKEY) needs the caller to
// remember and re-supply the curve id on several calls.
nid Nid
// ks_flag with .flexible value allowing
// flexible-size seed bytes as key.
// When it is `.fixed`, it will use the underlying key size.
ks_flag KeyFlag = .fixed
// ks_size stores size of the seed bytes when ks_flag was .flexible.
// You should set it to a non zero value
ks_size int
}
PrivateKey represents ECDSA private key. Actually its a key pair, contains private key and public key parts.
fn (PrivateKey) sign #
fn (pv PrivateKey) sign(message []u8, opt SignerOpts) ![]u8
sign performs signing the message with the options. By default options, it will perform hashing before signing the message.
hash_config: .with_custom_hash is NOT implemented for the default mbedTLS backend: mbedtls_ecdsa_write_signature's RFC 6979 deterministic- nonce path (compiled in for this build) needs a real mbedtls_md_type_t to seed its internal HMAC-DRBG, and hash.Hash (an arbitrary caller- supplied hasher) carries no such identifier -- passing MBEDTLS_MD_NONE through, which an earlier version of this file's own doc comments claimed was harmless, is actually rejected outright by mbedtls_md_info_from_type()/mbedtls_ecdsa_sign_det_restartable(). Verify() has no equivalent problem (mbedtls_ecdsa_read_signature takes no md_alg parameter at all), so only signing is restricted here. Build with -d use_openssl if you need custom-hash signing today.
fn (PrivateKey) sign_with_options #
fn (pv PrivateKey) sign_with_options(message []u8, opt SignerOpts) ![]u8
sign_with_options signs message with the options. It will be deprecated, Use PrivateKey.sign() instead.
fn (PrivateKey) bytes #
fn (pv PrivateKey) bytes() ![]u8
bytes represent private key as bytes.
fn (PrivateKey) seed #
fn (pv PrivateKey) seed() ![]u8
seed gets the seed (private key bytes). It will be deprecated. Use PrivateKey.bytes() instead.
fn (PrivateKey) public_key #
fn (pv PrivateKey) public_key() !PublicKey
public_key gets the PublicKey from private key.
fn (PrivateKey) equal #
fn (priv_key PrivateKey) equal(other PrivateKey) bool
equal compares two private keys was equal. Delegates to the public components' equality, matching OpenSSL's own EVP_PKEY_eq semantics of comparing public (not raw private-scalar) material.
fn (PrivateKey) free #
fn (pv &PrivateKey) free()
free clears out allocated memory for PrivateKey. Dont use PrivateKey after calling .free()
Double-free behavior is backend-dependent: -d use_openssl aborts the process on a true double-free (OpenSSL's EVP_PKEY_free); this default mbedTLS backend's mbedtls_ecdsa_free is documented safe on an already-freed/zeroed context (a silent no-op). Never rely on either -- call .free() exactly once.
struct PublicKey #
struct PublicKey {
ctx &C.mbedtls_ecdsa_context
mut:
nid Nid
}
PublicKey represents ECDSA public key for verifying message.
fn (PublicKey) bytes #
fn (pbk PublicKey) bytes() ![]u8
bytes gets the public key as an uncompressed EC point -- identical to PublicKey.uncompressed_bytes() (ecdsa_notd_use_openssl.v), which IS implemented for this backend; delegates rather than duplicating the mbedTLS call sequence.
fn (PublicKey) equal #
fn (pub_key PublicKey) equal(other PublicKey) bool
equal compares two public keys was equal.
fn (PublicKey) free #
fn (pb &PublicKey) free()
free clears out allocated memory for PublicKey. Dont use PublicKey after calling .free()
See PrivateKey.free()'s own doc comment for this backend's double-free behavior (a silent no-op here, unlike -d use_openssl's abort).
fn (PublicKey) uncompressed_bytes #
fn (pb PublicKey) uncompressed_bytes() ![]u8
uncompressed_bytes returns the public key as an uncompressed EC point, 0x04 || X || Y (SEC1 §2.3.3) — the wire format TLS 1.3's key_share extension uses for the secp256r1 (P-256) group (RFC 8446 §4.2.8.2).
fn (PublicKey) verify #
fn (pb PublicKey) verify(message []u8, sig []u8, opt SignerOpts) !bool
verify verifies a message with the signature are valid with public key provided . You should provide it with the same SignerOpts used with the .sign() call. or verify would fail (false).
struct SignerOpts #
struct SignerOpts {
pub mut:
// default to .with_recommended_hash
hash_config HashConfig = .with_recommended_hash
// make sense when HashConfig != with_recommended_hash
allow_smaller_size bool
allow_custom_hash bool
// set to non-nil if allow_custom_hash was true
custom_hash &hash.Hash = unsafe { nil }
}
SignerOpts represents configuration options to drive signing and verifying process.
- README
- Constants
- fn generate_key
- fn new_key_from_seed
- fn privkey_from_string
- fn pubkey_from_bytes
- fn pubkey_from_string
- fn HashConfig.from
- fn KeyFlag.from
- fn Nid.from
- fn PrivateKey.new
- fn PublicKey.from_uncompressed_bytes
- enum HashConfig
- enum Nid
- struct CurveOptions
- struct PrivateKey
- struct PublicKey
- struct SignerOpts