silk.crypto.ec.

Elliptic-curve keys, ECDSA, and ECDH on every named curve over a prime field that OpenSSL and Node.js list, such as brainpoolP256r1, secp256k1, secp224r1, and SM2.

When to use#

Use EcPrivateKey and EcPublicKey for Node.js crypto.createECDH, generateKeyPair('ec'), and crypto.sign with an ec key on any curve of silk.crypto.ec_key.EcCurve. For P-256, P-384, P-521, or secp256k1 alone, silk.crypto.p256, silk.crypto.p384, silk.crypto.p521, and silk.crypto.secp256k1 are faster.

Details#

An EcGroup holds the parameters of a curve y^2 = x^3 + ax + b modulo a prime p and its base point G of prime order n. A private key is a number d from 1 to n - 1 and its public key is the point dG. Points are read and written in the SEC1 forms: uncompressed 04 || x || y, compressed 02 or 03 || x, and hybrid 06 or 07 || x || y.

The signing functions take the digest of the message, not the message. A digest longer than n keeps its leftmost bits, as OpenSSL does. A signature is r || s, each big-endian and EcGroup.secretLength bytes, the IEEE P1363 form that Node.js calls ieee-p1363. EcSignature converts it to and from DER, the default of Node.js. EcPrivateKey.agree gives the x-coordinate of dQ, as Node.js ecdh.computeSecret does.

Field arithmetic uses Montgomery multiplication on 64-bit limbs, and points use the complete projective formulas of Renes, Costello, and Batina. Operations with a secret, such as EcPrivateKey.generate, EcPrivateKey.signDigest, and EcPrivateKey.agree, take the same time for every secret of a curve: they have no branch and no memory access that depends on the secret.

Errors#

  • EcError for a secret, a point, a length, or a signature that is not valid.

Gotchas#

On a curve with a cofactor other than 1, such as secp112r2, EcPublicKey.fromBytes also checks that the point is in the subgroup of the base point. Each signature needs a fresh secret from a cryptographically secure Random; a repeated or predictable one reveals the private key. The values stay in memory after use; nothing erases them.

Examples#

Agree on a secret on brainpoolP256r1#

silk
import silk.core.effect { Effect }
import silk.core.result { Result }
import silk.crypto.ec { EcError, EcGroup, EcPrivateKey }
import silk.crypto.ec_key { EcCurve }
import silk.num.u64
import silk.system.random { Random }

// A test provider. Never use it for keys.
struct Counter {
  state: u64,
}

impl Random for Counter {
  effect fn fillBytes(self: &mut Self, output: &mut [u8]) -> () {
    let mut index: usize = 0
    while index < output.length {
      self.state = u64.wrappingAdd(u64.wrappingMultiply(self.state, 6364136223846793005), 1)
      output[index] = u64.toU8(u64.shiftRight(self.state, 56))
      index = index + 1
    }
  }
}

effect fn agreeBoth() -> i32 ! EcError ? &mut Random {
  let group = EcGroup.named(EcCurve.BrainpoolP256r1)
  let alice = run EcPrivateKey.generate(&group)
  let bob = run EcPrivateKey.generate(&group)
  let mut fromAlice: [u8; 32] = [0; 32]
  let mut fromBob: [u8; 32] = [0; 32]
  if let Result<(), EcError>.Failure { error } = alice.agree(&bob.publicKey(), &mut fromAlice) {
    fail error
  }
  if let Result<(), EcError>.Failure { error } = bob.agree(&alice.publicKey(), &mut fromBob) {
    fail error
  }
  let mut i: usize = 0
  while i < 32 {
    if fromAlice[i] != fromBob[i] {
      return 1
    }
    i = i + 1
  }
  return 0
}

pub effect fn main() -> i32 ! EcError {
  let mut random = Counter { state: 1 }
  return run agreeBoth() |> Effect.provideMut<Random>(&mut random)
}

Import as EcPrivateKey with import silk.crypto.ec { EcPrivateKey }.

Public declarations: 6.

EcError#

silk
pub enum EcError

The reason that an elliptic-curve secret, point, length, or signature was rejected.

InvalidKey#

silk
InvalidKey

A secret is 0 or not less than the order n of the base point.

InvalidPoint#

silk
InvalidPoint

A point encoding is not valid: the first byte is not a SEC1 form, the length is wrong, a coordinate is not less than p, the point is not on the curve or not in the subgroup of the base point, the hybrid parity does not match, or the point is the point at infinity.

InvalidLength#

silk
InvalidLength

An input or output does not have the required length.

InvalidEncoding#

silk
InvalidEncoding

A DER signature is not strict DER of two non-negative integers.

InvalidSignature#

silk
InvalidSignature

The signature does not match the digest and the key.

InvalidSharedSecret#

silk
InvalidSharedSecret

The shared point of a key agreement is the point at infinity.

Implementation Debug for EcError#

silk
impl Debug for EcError

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> ()
    ! WriterError
    ? &mut Writer

Writes the name of the variant, such as InvalidKey.

EcPointFormat#

silk
pub enum EcPointFormat

The SEC1 form of an encoded public point.

Compressed#

silk
Compressed

02 or 03, by the parity of y, then x.

Uncompressed#

silk
Uncompressed

04, then x and y.

Hybrid#

silk
Hybrid

06 or 07, by the parity of y, then x and y.

Implementation Debug for EcPointFormat#

silk
impl Debug for EcPointFormat

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> ()
    ! WriterError
    ? &mut Writer

Writes the name of the variant, such as Compressed.

EcGroup#

silk
pub struct EcGroup

The parameters of a named curve over a prime field, with its base point and order.

When to use#

Make one with EcGroup.curveName and pass it to the key functions of this module. Keep it for all the operations on that curve.

Details#

Making a group converts the parameters to Montgomery form, which takes a few microseconds.

Gotchas#

It is a fixed-size value of about 1.5 KiB. Pass it by reference.

Implementation Copy for EcGroup#

silk
impl Copy for EcGroup

EcPublicKey#

silk
pub struct EcPublicKey

An elliptic-curve public key: a point of the subgroup of the base point other than the point at infinity.

When to use#

Use it to verify ECDSA signatures and as the peer key of EcPrivateKey.agree. Read one with EcPublicKey.fromBytes, or get one with EcPrivateKey.publicKey.

Gotchas#

It is a fixed-size value of about 1.7 KiB. Pass it by reference.

Implementation Copy for EcPublicKey#

silk
impl Copy for EcPublicKey

EcPrivateKey#

silk
pub struct EcPrivateKey

An elliptic-curve private key: a secret d from 1 to n - 1 and its public point dG.

When to use#

Use it to sign digests with ECDSA and to agree on a secret with ECDH. Generate one with EcPrivateKey.generate, or read one with EcPrivateKey.fromBytes.

Gotchas#

It does not erase its memory when dropped. It is a fixed-size value of about 1.8 KiB. Pass it by reference.

Implementation Copy for EcPrivateKey#

silk
impl Copy for EcPrivateKey

EcSignature#

silk
pub struct EcSignature

Conversions between the r || s form of an ECDSA signature and its DER form.

When to use#

Use it for Node.js dsaEncoding: der, the default, is the DER SEQUENCE of two INTEGERs, and ieee-p1363 is r || s, which EcPrivateKey.signDigest writes.

Implementation EcGroup#

silk
impl EcGroup

Associated function named#

silk
pub fn named(curve: EcCurve) -> EcGroup

Returns the group of the named curve curve.

Method curve#

silk
pub fn curve(self: &Self) -> EcCurve

Returns the named curve of the group.

Method fieldBits#

silk
pub fn fieldBits(self: &Self) -> usize

Returns the number of bits of the prime p, such as 256 for brainpoolP256r1.

Method orderBits#

silk
pub fn orderBits(self: &Self) -> usize

Returns the number of bits of the order n of the base point, such as 161 for secp160r1.

Method cofactor#

silk
pub fn cofactor(self: &Self) -> u32

Returns the cofactor: the number of points of the curve divided by n, such as 1 for most curves and 4 for secp112r2.

Method fieldLength#

silk
pub fn fieldLength(self: &Self) -> usize

Returns the length in bytes of a field element: the length of a shared secret from EcPrivateKey.agree and of each coordinate of an encoded point.

Method secretLength#

silk
pub fn secretLength(self: &Self) -> usize

Returns the length in bytes of a secret, which is the byte length of n.

Method signatureLength#

silk
pub fn signatureLength(self: &Self) -> usize

Returns the length in bytes of a signature in the r || s form: twice EcGroup.secretLength.

Method encodedLength#

silk
pub fn encodedLength(self: &Self, format: EcPointFormat) -> usize

Returns the length in bytes of a point encoded in format: EcGroup.fieldBytes plus 1 when compressed, and twice it plus 1 otherwise.

Implementation EcPublicKey#

silk
impl EcPublicKey

Associated function fromBytes#

silk
pub fn fromBytes(group: &EcGroup, encoded: &[u8]) -> Result<EcPublicKey, EcError>

Returns the public key for the SEC1 point encoded on group: compressed, uncompressed, or hybrid, as Node.js ecdh.computeSecret and ECDH.convertKey accept.

The time depends on the point, which is public.

Errors#

  • InvalidPoint if the encoding is not valid, a coordinate is not less than p, the point is not on the curve, a hybrid parity does not match y, or, on a curve with a cofactor other than 1, the point is not in the subgroup of the base point. The point at infinity is rejected.

Method group#

silk
pub fn group(self: &Self) -> EcGroup

Returns the group of the key.

Method write#

silk
pub fn write(self: &Self, format: EcPointFormat, output: &mut [u8]) -> Result<usize, EcError>

Writes the point in format to the start of output, and returns its length, which is EcGroup.encodedLength. The bytes of output after it do not change.

Errors#

  • InvalidLength if output is shorter than the encoding.

Method verifyDigest#

silk
pub fn verifyDigest(self: &Self, digest: &[u8], signature: &[u8]) -> Result<(), EcError>

Checks that signature is a valid ECDSA signature in the r || s form of the message with the digest digest.

A digest longer than n keeps its leftmost EcGroup.orderBits bits, as OpenSSL does.

Errors#

  • InvalidLength if signature is not EcGroup.signatureLength bytes.

  • InvalidSignature if r or s is not from 1 to n - 1, or the signature does not match.

Implementation EcPrivateKey#

silk
impl EcPrivateKey

Associated function generate#

silk
pub effect fn generate(group: &EcGroup) -> EcPrivateKey
    ? &mut Random

Returns a new key on group, with a secret from the ambient Random.

The secret is uniform from 1 to n - 1. The time of the public point does not depend on the secret. The Random provider must be cryptographically secure.

Associated function fromBytes#

silk
pub fn fromBytes(group: &EcGroup, secret: &[u8]) -> Result<EcPrivateKey, EcError>

Returns the private key for the big-endian secret secret on group, and computes its public point, as Node.js ecdh.setPrivateKey does. Leading zero bytes are allowed.

The time depends only on the length of secret.

Errors#

  • InvalidKey if the secret is 0 or not less than n.

Method group#

silk
pub fn group(self: &Self) -> EcGroup

Returns the group of the key.

Method publicKey#

silk
pub fn publicKey(self: &Self) -> EcPublicKey

Returns the public half of the key.

Method writeSecret#

silk
pub fn writeSecret(self: &Self, output: &mut [u8]) -> Result<(), EcError>

Writes the secret to all of output, big-endian with leading zero bytes, as Node.js ecdh.getPrivateKey does.

Errors#

Gotchas#

The bytes are the private key. Keep them secret.

Method signDigest#

silk
pub effect fn signDigest(self: &Self, digest: &[u8], signature: &mut [u8]) -> ()
    ! EcError
    ? &mut Random

Signs the message with the digest digest with ECDSA and writes the signature to signature in the r || s form. It reads the per-signature secret k from the ambient Random.

A digest longer than n keeps its leftmost EcGroup.orderBits bits, as OpenSSL does. The time does not depend on the secret or on k.

Errors#

Gotchas#

The Random provider must be cryptographically secure: a repeated or biased k reveals the private key.

Method agree#

silk
pub fn agree(self: &Self, peer: &EcPublicKey, output: &mut [u8]) -> Result<(), EcError>

Writes the shared secret with the peer key peer to output: the x-coordinate of dQ, big-endian and EcGroup.fieldBytes bytes, as Node.js ecdh.computeSecret and crypto.diffieHellman do.

The time does not depend on the secret.

Errors#

  • InvalidLength if output is not EcGroup.fieldBytes bytes.

  • InvalidPoint if peer is on another curve.

  • InvalidSharedSecret if the shared point is the point at infinity.

Gotchas#

Pass the secret through a key derivation function, such as silk.crypto.hkdf, before you use it as a key.

Implementation EcSignature#

silk
impl EcSignature

Associated function derLength#

silk
pub fn derLength(signature: &[u8]) -> usize

Returns the length of the DER form of the r || s signature signature.

Gotchas#

The program traps if signature has an odd length or is longer than 144 bytes.

Associated function toDer#

silk
pub fn toDer(signature: &[u8], output: &mut [u8]) -> Result<usize, EcError>

Writes the DER form of the r || s signature signature to the start of output, and returns its length, which is EcSignature.derLength. The bytes of output after the DER do not change.

Errors#

  • InvalidLength if output is shorter than the DER.

Gotchas#

The program traps if signature has an odd length or is longer than 144 bytes.

Associated function fromDer#

silk
pub fn fromDer(der: &[u8], output: &mut [u8]) -> Result<(), EcError>

Reads the DER signature der and writes it to all of output in the r || s form, each half output.length / 2 bytes, such as EcGroup.signatureLength.

Errors#

  • InvalidEncoding if der is not strict DER of two non-negative integers.

  • InvalidSignature if r or s is 0 or does not fit in half of output.

  • InvalidLength if output has an odd length.

Category: Cryptography.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2, x86_64-pc-windows-msvc, wasm64-unknown-unknown.

View the source of this page