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
- A private key's material is
Secret<bytes>. TheSecretwrapper bars the material from renderers, derives and comparisons. Reading it back isreveal, and the places here that reveal are the constructors checking the material andsignandecdhhanding it to the native primitive. An ECDH shared secret and HKDF key material are plainbytes, the typeaead.keyandsha2.hmactake. Persist a key withto_secret, which returns the wrapped material, and rebuild it withp256_secretored25519_secret. - Verify before you trust.
verify?returns a bool and never raises: a signature that does not verify isfalse. Check the result. - Decoding validates. The public-key and signature types declare
whereinvariants, and a value decoded through theirDecodepasses the same checks as one fromp256_publicand its siblings. The generated.newis the same check, reporting avalidation.ValidationErrorin place of aPubkeyError. - A DER signature arrives from WebAuthn. A browser's
navigator.credentialsresponse contains the ASN.1SEQUENCE { INTEGER r, INTEGER s }, andp256_signature_from_derconverts it to the raw form the rest of the module takes. Anything but the exact structure isInvalidDer. - VAPID and ES256 are already raw. A JWS signature and a VAPID
Authorizationheader carry the 64-byter || swith no DER wrapper, sop256_signaturereads them directly.
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
- A digest or a MAC.
extra/sha2hashes and produces HMAC-SHA-256. This signs: a signature proves possession of the key, and a MAC proves that both ends possess it. - Encryption.
extra/aeadseals and opens. Encrypt the payload withaeadand sign it or agree on a key with the surfaces here. - Textual transport.
extra/base64andextra/hexturn the raw bytes into text a JSON field or a header stores. - Raw entropy.
extra/randomdraws bytes. This derives keys and signs with them.
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