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, PrivateKey.new, .sign, .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.
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:
new_key_from_seed-- OpenSSL's current implementation performs no range check against the curve order; mbedTLS's closest equivalent validates the resulting scalar is in[1, curve_order-1]and rejects it otherwise. A seed that "works" under-d use_opensslcould be rejected here, so this is a genuine behavioral divergence, not just missing plumbing, and needs its own design pass before porting.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 is NOT implemented for the default mbedTLS backend: unlike OpenSSL's current implementation (raw BN_bin2bn(seed), no range check), mbedTLS's closest equivalent validates the resulting scalar is in [1, curve_order-1] and rejects it otherwise -- a seed that deterministically "works" under -d use_openssl today could be rejected here. That's a real behavioral divergence, not just missing plumbing, and needs its own design pass (reduce mod n first? accept the stricter rejection and document it?) before porting -- not silently reimplemented as if the two were equivalent. Build with -d use_openssl if you need this today.
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