hanki

pubkey

stdlib/extra/pubkey.hk: P-256 ECDSA and ECDH, Ed25519, and HKDF-SHA-256.

Signatures, key agreement and key derivation for the protocols that use them: JOSE ES256, Web Push VAPID, WebAuthn, and the HKDF step that turns a shared secret into keys. One algorithm per surface and no selector, as aead has. A caller who can pick an algorithm has to know which to pick, and the wrong answer is silent.

A pure-Hanki face over the sys native seam (HANKI.md §4, §17), like sqlite: nothing here bears an @intrinsic, and every call delegates to a sys.p256_*, sys.ed25519_* or sys.hkdf_* primitive. The primitives are pure, a signature being a deterministic function of its inputs, and only minting a key charges an effect, the [random] it draws. The native work sits in hanki_rtcore::pubkey, which both tiers call.

Encodings

Strict, and one per algorithm. A P-256 secret key is the 32-byte big-endian scalar. A P-256 public key is the 65-byte SEC1 uncompressed point 0x04 || X || Y, and the 33-byte compressed form is refused. A P-256 signature is the 64-byte raw r || s of JOSE ES256 and Web Push VAPID, produced with s in its low half and accepted with either, and p256_signature_from_der reads the ASN.1 DER form WebAuthn verifies with. Ed25519 follows RFC 8032: a 32-byte seed, a 32-byte public key and a 64-byte signature. HKDF-SHA-256 follows RFC 5869 with a 32-byte pseudorandom key.

What you have to get right

Audit coverage

The primitives are RustCrypto's, and their audit coverage is partial. zkSecurity's 2025 p256 report covers verification and excludes constant-time assurance and ECDH. ed25519-dalek's Quarkslab audit describes Ed25519 coverage as marginal. The p256 README has an unaudited-arithmetic warning.

Arriving here for something this module does not have

p256secretlength

p256_secret_length: int = 32

Bytes in a P-256 secret scalar.

p256publiclength

p256_public_length: int = 65

Bytes in an uncompressed SEC1 P-256 public point.

p256signaturelength

p256_signature_length: int = 64

Bytes in a raw r || s P-256 signature.

ed25519secretlength

ed25519_secret_length: int = 32

Bytes in an Ed25519 seed.

ed25519publiclength

ed25519_public_length: int = 32

Bytes in an Ed25519 public key.

ed25519signaturelength

ed25519_signature_length: int = 64

Bytes in an Ed25519 signature.

hkdfprklength

hkdf_prk_length: int = 32

Bytes in an HKDF-SHA-256 pseudorandom key.

hkdfmaxlength

hkdf_max_length: int = 8160

The most bytes hkdf_expand produces, 255 blocks of 32, the range the counter byte addresses.

PubkeyError

type PubkeyError
  BadSecretLength(int)
  BadSeedLength(int)
  BadPublicLength(int)
  BadSignatureLength(int)
  BadPrkLength(int)
  BadExpandLength(int)
  InvalidScalar
  InvalidPoint
  InvalidDer
end

What went wrong.

One sum covers all four surfaces. The length errors name the surface's own length, InvalidScalar is the way a P-256 scalar can be well-formed and still wrong, InvalidPoint the way a public key of either algorithm can be, and InvalidDer is the DER parser's single answer.

impl Display<PubkeyError>

to_string

def to_string(self) -> string

Lowercase and without a trailing stop, which fits it inside a larger sentence as well as alone.

BadSecretLength(5).to_string() => "a P-256 secret is 32 bytes, got 5"
BadSeedLength(0).to_string() => "an Ed25519 seed is 32 bytes, got 0"
BadPublicLength(33).to_string() => "a public key is 65 bytes for P-256 or 32 for Ed25519, got 33"
BadSignatureLength(63).to_string() => "a signature is 64 bytes, got 63"
BadPrkLength(31).to_string() => "a pseudorandom key is 32 bytes, got 31"
BadExpandLength(0).to_string() => "HKDF-Expand produces 1 through 8160 bytes, got 0"
InvalidScalar.to_string() => "the P-256 scalar is zero or at or above the curve order"
InvalidPoint.to_string() => "the public key is not a valid curve point"
InvalidDer.to_string() => "the DER signature encoding is invalid"

impl Eq<PubkeyError>

eq?

def eq?(self, other: Self) -> bool

Structural equality; the length errors compare the length they carry.

(InvalidScalar == InvalidScalar) => true
(BadSecretLength(5) == BadSecretLength(6)) => false
(BadSecretLength(5) == BadSeedLength(5)) => false

P256Public

opaque P256Public
  raw: bytes
where
  sys.p256_public_valid?(raw) else "a P-256 public key is a 65-byte uncompressed point on the curve"
end

A P-256 public key: the 65-byte SEC1 uncompressed point 0x04 || X || Y.

Opaque, with a where invariant that the bytes are an uncompressed point on the curve. p256_public, the generated P256Public.new and its Decode all check it, and p256_secret derives one from a secret. p256_public reports InvalidPoint for an off-curve point or a wrong tag.

impl P256Public

to_bytes

def to_bytes(self) -> bytes

The 65 bytes, for storing or sending.

p256_public(hex.decode("0460fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb67903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299").unwrap_or("".to_bytes())).map(|k| k.to_bytes().length).unwrap_or(0) => 65

verify?

def verify?(self, message: bytes, signature: P256Signature) -> bool

Verify a 64-byte raw r || s signature over message.

false when the check fails, including an r or s of zero or at or above the curve order. It never raises, a failed verification being an answer.

p = p256_public(hex.decode("0460fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb67903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299").unwrap_or("".to_bytes())).or_crash!("the RFC 6979 public key parses")
s = p256_signature(hex.decode("f1abb023518351cd71d881567b1ea663ed3efcf6c5132b354f28d3b0b7d38367019f4113742a2b14bd25926b49c649155f267e60d3814b4c0cc84250e46f0083").unwrap_or("".to_bytes())).or_crash!("the RFC 6979 signature parses")
p.verify?("test".to_bytes(), s) => true
p.verify?("tesu".to_bytes(), s) => false

impl Eq<P256Public>

eq?

def eq?(self, other: Self) -> bool

An ordinary byte compare. A public key is public material, and an ordinary compare of it leaks nothing.

k = p256_public(hex.decode("0460fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb67903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299").unwrap_or("".to_bytes())).or_crash!("the RFC 6979 public key parses")
(k == p256_public(k.to_bytes()).or_crash!("the key round-trips")) => true

impl Display<P256Public>

to_string

def to_string(self) -> string

Lowercase hex, 130 digits. A public key is public and rendering one is safe, which makes it loggable and comparable in a debug line.

p256_public(hex.decode("0460fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb67903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299").unwrap_or("".to_bytes())).map(|k| k.to_string().length).unwrap_or(0) => 130

P256Signature

opaque P256Signature
  raw: bytes
where
  raw.length == p256_signature_length else "a P-256 signature is 64 bytes"
end

A P-256 signature: the 64-byte raw r || s.

Opaque, with a where invariant that the bytes are 64 bytes. p256_signature, the generated P256Signature.new and its Decode all check it, and sign and p256_signature_from_der each yield one. p256_signature reads the raw form JOSE ES256 and Web Push VAPID use; p256_signature_from_der reads the ASN.1 form WebAuthn uses.

impl P256Signature

to_bytes

def to_bytes(self) -> bytes

The 64 bytes.

p256_signature(hex.decode("f1abb023518351cd71d881567b1ea663ed3efcf6c5132b354f28d3b0b7d38367019f4113742a2b14bd25926b49c649155f267e60d3814b4c0cc84250e46f0083").unwrap_or("".to_bytes())).map(|s| s.to_bytes().length).unwrap_or(0) => 64

impl Eq<P256Signature>

eq?

def eq?(self, other: Self) -> bool

A signature is public material, and an ordinary byte compare of it leaks nothing. The compare is what lets a caller check a signature it parsed against the one it has.

k = p256_signature(hex.decode("f1abb023518351cd71d881567b1ea663ed3efcf6c5132b354f28d3b0b7d38367019f4113742a2b14bd25926b49c649155f267e60d3814b4c0cc84250e46f0083").unwrap_or("".to_bytes())).or_crash!("the RFC 6979 signature parses")
(k == p256_signature(k.to_bytes()).or_crash!("the signature round-trips")) => true

P256Secret

opaque P256Secret
  material: Secret<bytes>
  public_key: P256Public
end

A P-256 secret key: the 32-byte big-endian scalar and the public point it derives.

Opaque, and holding the material in a Secret<bytes>, which bars it from renderers, derives and comparisons. The derived public point is stored beside it, which makes public a field read with no error case.

impl P256Secret

public

prop public(self) -> P256Public

The public key the secret derives, as a P256Public.

p256_secret(Secret.hide(hex.decode("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721").unwrap_or("".to_bytes()))).map(|s| s.public.to_bytes().length).unwrap_or(0) => 65

sign

def sign(self, message: bytes) -> P256Signature

Sign message under this key, returning the 64-byte raw r || s.

The message is hashed with SHA-256 and the nonce is derived by RFC 6979, which makes the signature a deterministic function of the key and the message, and s is normalized to its low half. The material is revealed straight into the native primitive.

p256_secret(Secret.hide(hex.decode("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721").unwrap_or("".to_bytes()))).map(|s| s.sign("test".to_bytes()).to_bytes().length).unwrap_or(0) => 64

ecdh

def ecdh(self, peer: P256Public) -> bytes

The 32-byte x-coordinate shared with peer, an ECDH agreement.

The material is revealed straight into the native primitive. peer is a P256Public, which checks the point at construction, leaving this with no error case.

a = p256_secret(Secret.hide(hex.decode("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721").unwrap_or("".to_bytes()))).or_crash!("the first key parses")
b = p256_secret(Secret.hide(hex.decode("0000000000000000000000000000000000000000000000000000000000000001").unwrap_or("".to_bytes()))).or_crash!("the second key parses")
a.ecdh(b.public).length => 32

to_secret

def to_secret(self) -> Secret<bytes>

The wrapped material, for storing in a keychain or a config.

It returns the Secret, and not the bytes, which leaves the caller to reveal at the one place the key is used. Rebuild the same key with p256_secret.

p256_secret(Secret.hide(hex.decode("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721").unwrap_or("".to_bytes()))).map(|s| s.to_secret().reveal().length).unwrap_or(0) => 32

p256_secret

def p256_secret(material: Secret<bytes>) -> Result<P256Secret, PubkeyError>

Take 32 bytes as a P-256 secret key, reading one back from a keychain or a config, and derive its public point.

The length is checked here, and the scalar is checked against the curve order: a zero or an out-of-range value is InvalidScalar. To make a new key use p256_new_secret!, which cannot get that wrong.

p256_secret(Secret.hide(hex.decode("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721").unwrap_or("".to_bytes()))).map(|s| s.public.to_bytes().length).unwrap_or(0) => 65
p256_secret(Secret.hide("short".to_bytes())).map(|s| s.public.to_bytes().length).unwrap_or(-1) => -1
p256_secret(Secret.hide(hex.decode("0000000000000000000000000000000000000000000000000000000000000000").unwrap_or("".to_bytes()))).map(|s| s.public.to_bytes().length).unwrap_or(-1) => -1

p256newsecret!

def p256_new_secret!() -> P256Secret [random]

A fresh P-256 key, drawn from the same source random.bytes! draws from.

Charges [random], and a program that mints keys says so in its signature and a run can refuse it with --deny random. Under --deterministic the draw comes from the seeded stream and is not cryptographic, which is what makes a test reproducible and what makes that mode unfit for real keys.

A draw that is zero or at or above the curve order is InvalidScalar, and the loop draws again. The chance of a redraw is about 2^-32, the curve order sitting just below 2^256.

The key it hands back is persistable: to_secret reads the wrapped material out and p256_secret builds the same key back from it.

p256_new_secret!().public.to_bytes().length => 65

p256_public

def p256_public(raw: bytes) -> Result<P256Public, PubkeyError>

Take 65 bytes as a P-256 public key: the SEC1 uncompressed point.

The length must be 65, or it is BadPublicLength. The point must then be an uncompressed point on the curve, with a leading 0x04, or it is InvalidPoint.

p256_public(hex.decode("0460fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb67903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299").unwrap_or("".to_bytes())).map(|k| k.to_bytes().length).unwrap_or(0) => 65
p256_public("short".to_bytes()) == Err(BadPublicLength(5)) => true
p256_public(hex.decode("0260fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb6").unwrap_or("".to_bytes())) == Err(BadPublicLength(33)) => true
p256_public(hex.decode("0460fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb67903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462298").unwrap_or("".to_bytes())) == Err(InvalidPoint) => true

p256_signature

def p256_signature(raw: bytes) -> Result<P256Signature, PubkeyError>

Take 64 bytes as a raw P-256 signature, the r || s of JOSE ES256 and Web Push VAPID.

p256_signature(hex.decode("f1abb023518351cd71d881567b1ea663ed3efcf6c5132b354f28d3b0b7d38367019f4113742a2b14bd25926b49c649155f267e60d3814b4c0cc84250e46f0083").unwrap_or("".to_bytes())).map(|s| s.to_bytes().length).unwrap_or(0) => 64
p256_signature("short".to_bytes()) == Err(BadSignatureLength(5)) => true

p256signaturefrom_der

def p256_signature_from_der(raw: bytes) -> Result<P256Signature, PubkeyError>

Parse the ASN.1 DER form WebAuthn verifies with, a strict SEQUENCE { INTEGER r, INTEGER s }, into the 64-byte raw r || s.

Strict means the exact structure: the outer tag and short length must match the input, each integer must be present and non-empty, a negative integer (a high bit on its first content byte) is InvalidDer, a leading 0x00 that the high bit did not require is InvalidDer, and an integer longer than 32 content bytes is InvalidDer. Each integer is left-padded with zeroes to 32 bytes.

p256_signature_from_der(hex.decode("3045022100efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf371602200834e36ad29a83bf2bc9385e491d6099c8fdf9d1ed67aa7ea5f51f93782857a9").unwrap_or("".to_bytes())).map(|s| s.to_bytes().length).unwrap_or(0) => 64
p256_signature_from_der("not der".to_bytes()) == Err(InvalidDer) => true

Ed25519Public

opaque Ed25519Public
  raw: bytes
where
  sys.ed25519_public_valid?(raw) else "an Ed25519 public key is 32 bytes that decompress to a curve point"
end

An Ed25519 public key: 32 bytes, RFC 8032.

Opaque, with a where invariant that the bytes decompress to a curve point. ed25519_public, the generated Ed25519Public.new and its Decode all check it, and ed25519_secret derives one from a seed.

impl Ed25519Public

to_bytes

def to_bytes(self) -> bytes

The 32 bytes, for storing or sending.

ed25519_public(hex.decode("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a").unwrap_or("".to_bytes())).map(|k| k.to_bytes().length).unwrap_or(0) => 32

verify?

def verify?(self, message: bytes, signature: Ed25519Signature) -> bool

Verify a 64-byte signature over message.

Strict RFC 8032 verification: false on a small-order public key or R, a non-canonical s, or a failed check. It never raises, a failed verification being an answer.

p = ed25519_public(hex.decode("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a").unwrap_or("".to_bytes())).or_crash!("the RFC 8032 test 1 key parses")
s = ed25519_signature(hex.decode("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b").unwrap_or("".to_bytes())).or_crash!("the RFC 8032 test 1 signature parses")
p.verify?("".to_bytes(), s) => true
p.verify?("x".to_bytes(), s) => false

impl Eq<Ed25519Public>

eq?

def eq?(self, other: Self) -> bool

An ordinary byte compare. A public key is public material, and an ordinary compare of it leaks nothing.

k = ed25519_public(hex.decode("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a").unwrap_or("".to_bytes())).or_crash!("the RFC 8032 test 1 key parses")
(k == ed25519_public(k.to_bytes()).or_crash!("the key round-trips")) => true

Ed25519Signature

opaque Ed25519Signature
  raw: bytes
where
  raw.length == ed25519_signature_length else "an Ed25519 signature is 64 bytes"
end

An Ed25519 signature: 64 bytes, RFC 8032.

Opaque, with a where invariant that the bytes are 64 bytes. ed25519_signature, the generated Ed25519Signature.new and its Decode all check it, and sign yields one.

impl Ed25519Signature

to_bytes

def to_bytes(self) -> bytes

The 64 bytes.

ed25519_signature(hex.decode("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b").unwrap_or("".to_bytes())).map(|s| s.to_bytes().length).unwrap_or(0) => 64

impl Eq<Ed25519Signature>

eq?

def eq?(self, other: Self) -> bool

A signature is public material, and an ordinary byte compare of it leaks nothing.

k = ed25519_signature(hex.decode("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b").unwrap_or("".to_bytes())).or_crash!("the RFC 8032 test 1 signature parses")
(k == ed25519_signature(k.to_bytes()).or_crash!("the signature round-trips")) => true

Ed25519Secret

opaque Ed25519Secret
  material: Secret<bytes>
  public_key: Ed25519Public
end

An Ed25519 secret key: the 32-byte seed and the public key it derives.

Opaque, and holding the material in a Secret<bytes>, for the reasons P256Secret gives.

impl Ed25519Secret

public

prop public(self) -> Ed25519Public

The public key the seed derives, as an Ed25519Public.

ed25519_secret(Secret.hide(hex.decode("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60").unwrap_or("".to_bytes()))).map(|s| s.public.to_bytes().length).unwrap_or(0) => 32

sign

def sign(self, message: bytes) -> Ed25519Signature

Sign message under this seed, returning the 64-byte signature.

The material is revealed straight into the native primitive.

ed25519_secret(Secret.hide(hex.decode("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60").unwrap_or("".to_bytes()))).map(|s| s.sign("".to_bytes()).to_bytes().length).unwrap_or(0) => 64

to_secret

def to_secret(self) -> Secret<bytes>

The wrapped seed, for storing in a keychain or a config.

It returns the Secret, and not the bytes. Rebuild the same key with ed25519_secret.

ed25519_secret(Secret.hide(hex.decode("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60").unwrap_or("".to_bytes()))).map(|s| s.to_secret().reveal().length).unwrap_or(0) => 32

ed25519_secret

def ed25519_secret(material: Secret<bytes>) -> Result<Ed25519Secret, PubkeyError>

Take 32 bytes as an Ed25519 seed and derive its public key.

A seed of the wrong length is BadSeedLength; every 32-byte seed is valid, and the native primitive's only None is the wrong length. To make a new key use ed25519_new_secret!.

ed25519_secret(Secret.hide(hex.decode("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60").unwrap_or("".to_bytes()))).map(|s| s.public.to_bytes().length).unwrap_or(0) => 32
ed25519_secret(Secret.hide("short".to_bytes())).map(|s| s.public.to_bytes().length).unwrap_or(-1) => -1

ed25519newsecret!

def ed25519_new_secret!() -> Ed25519Secret [random]

A fresh Ed25519 key, drawn from the same source random.bytes! draws from.

Charges [random], with the --deterministic caveat p256_new_secret! states. Every 32-byte seed is valid, and the redraw arm is never taken.

The key is persistable: to_secret reads the wrapped material out and ed25519_secret builds the same key back from it.

ed25519_new_secret!().public.to_bytes().length => 32

ed25519_public

def ed25519_public(raw: bytes) -> Result<Ed25519Public, PubkeyError>

Take 32 bytes as an Ed25519 public key, RFC 8032.

The length must be 32, or it is BadPublicLength. The bytes must then decompress to a curve point, or it is InvalidPoint.

ed25519_public(hex.decode("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a").unwrap_or("".to_bytes())).map(|k| k.to_bytes().length).unwrap_or(0) => 32
ed25519_public("short".to_bytes()) == Err(BadPublicLength(5)) => true
ed25519_public(hex.decode("0200000000000000000000000000000000000000000000000000000000000000").unwrap_or("".to_bytes())) == Err(InvalidPoint) => true

ed25519_signature

def ed25519_signature(raw: bytes) -> Result<Ed25519Signature, PubkeyError>

Take 64 bytes as an Ed25519 signature, RFC 8032.

ed25519_signature(hex.decode("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b").unwrap_or("".to_bytes())).map(|s| s.to_bytes().length).unwrap_or(0) => 64
ed25519_signature("short".to_bytes()) == Err(BadSignatureLength(5)) => true

hkdf_extract

def hkdf_extract(salt: bytes, ikm: bytes) -> bytes

The RFC 5869 HKDF-SHA-256 extract step: the 32-byte pseudorandom key for salt and ikm.

Total, and always 32 bytes. An empty salt is the zero salt RFC 5869 specifies for a caller who supplies none.

hex.encode(hkdf_extract(hex.decode("000102030405060708090a0b0c").unwrap_or("".to_bytes()), hex.decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap_or("".to_bytes()))) => "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5"

hkdf_expand

def hkdf_expand(prk: bytes, info: bytes, length: i32) -> Result<bytes, PubkeyError>

The RFC 5869 HKDF-SHA-256 expand step: length bytes from a 32-byte prk under info.

Err(BadPrkLength) when prk is not 32 bytes, and Err(BadExpandLength) when length is outside 1 through hkdf_max_length, the range the counter byte addresses. With a valid prk the native primitive's only None is the length.

prk = hex.decode("077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5").unwrap_or("".to_bytes())
hkdf_expand(prk, hex.decode("f0f1f2f3f4f5f6f7f8f9").unwrap_or("".to_bytes()), 42i32).map(|okm| okm.length).unwrap_or(0) => 42
hkdf_expand(prk, "".to_bytes(), 0i32) == Err(BadExpandLength(0)) => true
hkdf_expand("short".to_bytes(), "".to_bytes(), 1i32) == Err(BadPrkLength(5)) => true

_DerInteger

struct _DerInteger
  content: bytes
  next: int
end

One DER INTEGER from start: its content with any required leading 0x00 stripped, and the offset just past it.

derinteger

def _der_integer(raw: bytes, start: int) -> Result<_DerInteger, PubkeyError>

Parse one strict DER INTEGER at start. Negative, non-minimal, empty and overlong integers are InvalidDer, as is a missing or malformed tag and length.

leftpad_scalar

def _left_pad_scalar(part: bytes) -> bytes

Left-pad a DER integer's content with zeroes to the 32-byte scalar width.

_zeros

def _zeros(count: int) -> bytes

count zero bytes.

_validated!

def _validated!<T, D: deserializer.Deserializer>(d: D, built: Result<T, validation.ValidationError>) -> Result<T, deserializer.DecodeError>

A decoded value through its generated .new: bytes the type's where invariant refuses are deserializer.InvalidValue.

impl Hash<P256Public>

hash

prop hash(self) -> u64

Hashes the bytes Eq compares.

k = p256_public(hex.decode("0460fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb67903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299").unwrap_or("".to_bytes())).or_crash!("the RFC 6979 public key parses")
(k.hash == p256_public(k.to_bytes()).or_crash!("the value round-trips").hash) => true

impl Encode<P256Public>

encode!

def encode!<S: serializer.Serializer>(self, s: S) -> ()

Writes the raw bytes as one byte string.

k = p256_public(hex.decode("0460fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb67903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299").unwrap_or("".to_bytes())).or_crash!("the RFC 6979 public key parses")
got: Result<P256Public, deserializer.DecodeError> = codec.from_bytes(codec.to_bytes(k))
got.map(|x| x == k).unwrap_or(false) => true

impl Decode<P256Public>

decode!

def decode!<D: deserializer.Deserializer>(d: D, depth: int) -> Result<P256Public, deserializer.DecodeError>

Reads the byte string Encode writes through the generated .new. Bytes the where invariant refuses are deserializer.InvalidValue.

@no-doctest: the Encode example round-trips through it

impl Hash<P256Signature>

hash

prop hash(self) -> u64

Hashes the bytes Eq compares.

k = p256_signature(hex.decode("f1abb023518351cd71d881567b1ea663ed3efcf6c5132b354f28d3b0b7d38367019f4113742a2b14bd25926b49c649155f267e60d3814b4c0cc84250e46f0083").unwrap_or("".to_bytes())).or_crash!("the RFC 6979 signature parses")
(k.hash == p256_signature(k.to_bytes()).or_crash!("the value round-trips").hash) => true

impl Encode<P256Signature>

encode!

def encode!<S: serializer.Serializer>(self, s: S) -> ()

Writes the raw bytes as one byte string.

k = p256_signature(hex.decode("f1abb023518351cd71d881567b1ea663ed3efcf6c5132b354f28d3b0b7d38367019f4113742a2b14bd25926b49c649155f267e60d3814b4c0cc84250e46f0083").unwrap_or("".to_bytes())).or_crash!("the RFC 6979 signature parses")
got: Result<P256Signature, deserializer.DecodeError> = codec.from_bytes(codec.to_bytes(k))
got.map(|x| x == k).unwrap_or(false) => true

impl Decode<P256Signature>

decode!

def decode!<D: deserializer.Deserializer>(d: D, depth: int) -> Result<P256Signature, deserializer.DecodeError>

Reads the byte string Encode writes through the generated .new. Bytes the where invariant refuses are deserializer.InvalidValue.

@no-doctest: the Encode example round-trips through it

impl Hash<Ed25519Public>

hash

prop hash(self) -> u64

Hashes the bytes Eq compares.

k = ed25519_public(hex.decode("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a").unwrap_or("".to_bytes())).or_crash!("the RFC 8032 test 1 key parses")
(k.hash == ed25519_public(k.to_bytes()).or_crash!("the value round-trips").hash) => true

impl Encode<Ed25519Public>

encode!

def encode!<S: serializer.Serializer>(self, s: S) -> ()

Writes the raw bytes as one byte string.

k = ed25519_public(hex.decode("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a").unwrap_or("".to_bytes())).or_crash!("the RFC 8032 test 1 key parses")
got: Result<Ed25519Public, deserializer.DecodeError> = codec.from_bytes(codec.to_bytes(k))
got.map(|x| x == k).unwrap_or(false) => true

impl Decode<Ed25519Public>

decode!

def decode!<D: deserializer.Deserializer>(d: D, depth: int) -> Result<Ed25519Public, deserializer.DecodeError>

Reads the byte string Encode writes through the generated .new. Bytes the where invariant refuses are deserializer.InvalidValue.

@no-doctest: the Encode example round-trips through it

impl Hash<Ed25519Signature>

hash

prop hash(self) -> u64

Hashes the bytes Eq compares.

k = ed25519_signature(hex.decode("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b").unwrap_or("".to_bytes())).or_crash!("the RFC 8032 test 1 signature parses")
(k.hash == ed25519_signature(k.to_bytes()).or_crash!("the value round-trips").hash) => true

impl Encode<Ed25519Signature>

encode!

def encode!<S: serializer.Serializer>(self, s: S) -> ()

Writes the raw bytes as one byte string.

k = ed25519_signature(hex.decode("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b").unwrap_or("".to_bytes())).or_crash!("the RFC 8032 test 1 signature parses")
got: Result<Ed25519Signature, deserializer.DecodeError> = codec.from_bytes(codec.to_bytes(k))
got.map(|x| x == k).unwrap_or(false) => true

impl Decode<Ed25519Signature>

decode!

def decode!<D: deserializer.Deserializer>(d: D, depth: int) -> Result<Ed25519Signature, deserializer.DecodeError>

Reads the byte string Encode writes through the generated .new. Bytes the where invariant refuses are deserializer.InvalidValue.

@no-doctest: the Encode example round-trips through it

_hex

def _hex(text: string) -> bytes

_secret

def _secret(text: string) -> Secret<bytes>

codecround_trips?

def _codec_round_trips?<T: Encode + Decode + Eq>(x: T) -> bool