silk.crypto.secp256k1.

Key agreement (ECDH) and ECDSA signatures on the curve secp256k1 of SEC 2.

When to use#

Use Secp256k1 for an ECDH key exchange or an ECDSA signature on secp256k1, such as Node.js crypto.createECDH('secp256k1'), crypto.sign with a secp256k1 key, or an ES256K JSON Web Token. Use EcdsaSecp256k1 to verify.

Details#

Public keys are 65 bytes in uncompressed SEC1 form: 0x04, then the X and Y coordinates, each 32 bytes big-endian. Secp256k1.compressPublicKey and Secp256k1.decompressPublicKey convert to and from the 33-byte compressed form. Secrets are 32 bytes big-endian. Secp256k1.agree returns the X coordinate of the shared point and consumes the key. Secp256k1.sign returns r and s, each 32 bytes, with the deterministic nonce of RFC 6979, as OpenSSL does; it does not lower s as Bitcoin requires. EcdsaSecp256k1.toDer and EcdsaSecp256k1.fromDer convert signatures to and from DER. Nothing allocates.

Errors#

Gotchas#

A valid signature proves only that the holder of the private key signed the message. It does not prove that you can trust the key. Both high and low s values verify. The key exchange does not authenticate the peer. Pass the shared secret through silk.crypto.hkdf before you use it as a key. The secret is not erased when it is dropped.

Examples#

Verify a signature#

silk
import silk.crypto.secp256k1 { EcdsaSecp256k1, Secp256k1SignatureError }
import silk.crypto.sha2 { Sha256 }
import silk.core.result { Result }

pub fn main() -> i32 {
  // A public key and a signature of "hello" that another program made.
  let publicKey = b"\x04\x8d\x3a\x31\x0b\x0d\xfa\x35\xd0\x46\x47\x18\x5c\x42\x12\xca\xe0\xcf\xb0\x3b\xf6\xa2\x9c\x77\xce\xef\x34\xdf\x21\x05\x63\x4d\xa8\x9b\x8d\x7a\x59\x80\x90\x82\xf5\x78\x25\x8a\xa3\x02\xed\xdd\x75\x0c\x2e\xff\xa8\x88\x86\xc3\x1e\x48\xe9\x28\xd1\x26\x06\x9e\x29"
  let signature = b"\xcc\x0f\x61\x5e\xc2\xc1\x99\x4f\xcf\xfc\xb5\xb2\xf0\x06\xda\x4b\xe4\x95\x62\x5f\x1d\xcb\x59\xcc\xed\xbc\xdb\xfa\xf9\x0b\x4a\x19\x58\x1b\x69\x32\x83\xad\x38\x65\x3f\x3f\xe6\x13\xbf\x23\xd8\x17\x2a\x22\x94\xdd\x3d\x1c\x4f\x38\xa1\xfb\xe8\x8a\x79\x28\x6b\x9d"
  match EcdsaSecp256k1.verify<Sha256>(publicKey, b"hello", signature) {
    Result<(), Secp256k1SignatureError>.Success {..} => {}
    Result<(), Secp256k1SignatureError>.Failure {..} => {
      return 1
    }
  }
  return match EcdsaSecp256k1.verify<Sha256>(publicKey, b"hellp", signature) {
    Result<(), Secp256k1SignatureError>.Failure { error: Secp256k1SignatureError.AuthenticationFailed } => 0
    _ => 1
  }
}

Sign and agree#

silk
import silk.crypto.secp256k1 { EcdsaSecp256k1, Secp256k1, Secp256k1Error, Secp256k1SignatureError }
import silk.crypto.sha2 { Sha256 }
import silk.core.result { Result }

pub fn main() -> i32 {
  // Real programs use `Secp256k1.generate()` with a secure `Random` provider.
  let mut secret: [u8; 32] = [0; 32]
  secret[31] = 7
  let key = match Secp256k1.fromBytes(&secret) {
    Result<Secp256k1, Secp256k1Error>.Success { value } => move value
    Result<Secp256k1, Secp256k1Error>.Failure {..} => {
      return 1
    }
  }
  let publicKey = key.publicKey()
  let signature = key.sign<Sha256>(b"hello")
  return match EcdsaSecp256k1.verify<Sha256>(&publicKey, b"hello", &signature) {
    Result<(), Secp256k1SignatureError>.Success {..} => 0
    Result<(), Secp256k1SignatureError>.Failure {..} => 1
  }
}

Import as Secp256k1 with import silk.crypto.secp256k1 { Secp256k1 }.

Public declarations: 4.

Secp256k1Error#

silk
pub enum Secp256k1Error

The reason that a secp256k1 secret or public key is not valid. It holds no secret bytes.

InvalidLength#

silk
InvalidLength

A secret is not 32 bytes, or a public key is not 65 bytes.

InvalidScalar#

silk
InvalidScalar

The secret is 0, or not less than the order of the curve.

InvalidEncoding#

silk
InvalidEncoding

The public key does not start with 0x04. Compressed keys are not supported.

InvalidPoint#

silk
InvalidPoint

A coordinate is not less than the field prime, or the point is not on the curve.

Implementation Debug for Secp256k1Error#

silk
impl Debug for Secp256k1Error

Operation debug#

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

Writes the name of the variant, such as InvalidLength.

Secp256k1#

silk
pub struct Secp256k1

A secp256k1 secret, for key agreement and for ECDSA signatures. It is not Copy.

When to use#

Use it for an ECDH key exchange or an ECDSA signature on secp256k1, such as Node.js crypto.createECDH('secp256k1') or an ES256K JSON Web Token.

Details#

Secp256k1.agree consumes it, so make a new one for each exchange. Secp256k1.sign does not. Public keys are 65 bytes in uncompressed SEC1 form: 0x04, then the X and Y coordinates, each 32 bytes big-endian. Secrets are 32 bytes big-endian. Nothing allocates.

Gotchas#

The key exchange does not authenticate the peer. Pass the shared secret through silk.crypto.hkdf before you use it as a key. The secret is not erased when it is dropped.

Implementation Secp256k1#

silk
impl Secp256k1

Associated function validatePublicKey#

silk
pub fn validatePublicKey(publicKey: &[u8]) -> Result<(), Secp256k1Error>

Checks that publicKey is a valid uncompressed secp256k1 public key.

It must be 65 bytes: 0x04, then X and Y, each 32 bytes big-endian and less than the field prime, and the point must be on the curve.

Errors#

  • InvalidLength, InvalidEncoding, or InvalidPoint.

Associated function fromBytes#

silk
pub fn fromBytes(bytes: &[u8]) -> Result<Secp256k1, Secp256k1Error>

Returns the secret for the 32 big-endian bytes bytes.

Errors#

  • InvalidLength if bytes is not 32 bytes.

  • InvalidScalar if the value is 0, or not less than the order of the curve.

Associated function generate#

silk
pub effect fn generate() -> Secp256k1 ? &mut Random

Returns a new random secret, with bytes from the ambient Random.

It draws 32 bytes again until the value is valid, so the result is uniform. The Random provider must be cryptographically secure. If it fails, the program stops.

Method publicKey#

silk
pub fn publicKey(self: &Self) -> [u8; 65]

Returns the 65-byte uncompressed public key to send to the peer. It does not consume the secret.

Method agree#

silk
pub fn agree(self: Self, peer: &[u8]) -> Result<[u8; 32], Secp256k1Error>

Returns the 32-byte shared secret for the public key peer, and consumes the secret.

The result is the big-endian X coordinate of the shared point.

Errors#

  • InvalidLength, InvalidEncoding, or InvalidPoint if peer is not a valid uncompressed public key. The secret is consumed also when it fails.

Gotchas#

Pass the result through silk.crypto.hkdf before you use it as a key.

Method toBytes#

silk
pub fn toBytes(self: &Self) -> [u8; 32]

Returns the 32 big-endian bytes of the secret, the input of Secp256k1.fromBytes.

Gotchas#

The bytes are the private key. Keep them secret.

Method sign#

silk
pub fn sign<H: Digest>(self: &Self, message: &[u8]) -> [u8; 64]

Returns the ECDSA signature of message, hashed with H, as 64 bytes: r, then s, each 32 bytes big-endian.

The nonce is deterministic (RFC 6979) with HMAC over H, so the same key, hash, and message always give the same signature. EcdsaSecp256k1.toDer converts the result to DER.

Gotchas#

The program traps if message is longer than H allows.

Method signDigest#

silk
pub fn signDigest<H: Digest>(self: &Self, digest: &[u8]) -> [u8; 64]

Returns the ECDSA signature of a message whose digest is digest, as 64 bytes: r, then s, each 32 bytes big-endian.

H is the hash for the RFC 6979 nonce, normally the hash that made digest. A digest longer than 32 bytes is cut to its first 32 bytes, as ECDSA requires.

Associated function compressPublicKey#

silk
pub fn compressPublicKey(publicKey: &[u8]) -> Result<[u8; 33], Secp256k1Error>

Returns the 33-byte compressed form of the uncompressed public key publicKey: 0x02 or 0x03 for the parity of Y, then X.

Errors#

  • InvalidLength, InvalidEncoding, or InvalidPoint if publicKey is not a valid uncompressed public key.

Associated function decompressPublicKey#

silk
pub fn decompressPublicKey(publicKey: &[u8]) -> Result<[u8; 65], Secp256k1Error>

Returns the 65-byte uncompressed form of the compressed public key publicKey.

Errors#

  • InvalidLength if publicKey is not 33 bytes.

  • InvalidEncoding if it does not start with 0x02 or 0x03.

  • InvalidPoint if X is not less than the field prime, or no point has that X.

Secp256k1SignatureError#

silk
pub enum Secp256k1SignatureError

The reason that an ECDSA secp256k1 verification failed.

InvalidKey#

silk
InvalidKey

The public key is not a valid uncompressed secp256k1 key.

InvalidEncoding#

silk
InvalidEncoding

The signature is not strict DER, or r or s is not between 1 and the order of the curve.

AuthenticationFailed#

silk
AuthenticationFailed

The inputs are valid, but the signature does not match the message and the key.

Implementation Debug for Secp256k1SignatureError#

silk
impl Debug for Secp256k1SignatureError

Operation debug#

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

Writes the name of the variant, such as InvalidKey.

EcdsaSecp256k1#

silk
pub struct EcdsaSecp256k1

ECDSA verification on secp256k1 with any hash, and conversion between the two signature forms.

When to use#

Use it to verify a signature in the fixed 64-byte form (r, then s), as in ES256K JSON Web Tokens. EcdsaSecp256k1.fromDer turns a DER signature, as Node.js crypto.verify takes it, into that form. To sign, use Secp256k1.sign.

Details#

Public keys are 65 bytes in uncompressed SEC1 form. A fixed-form signature is 64 bytes. A DER signature is a SEQUENCE of the two INTEGERs, at most 72 bytes. Nothing allocates.

Gotchas#

A valid signature proves only that the holder of the private key signed the message. It does not prove that you can trust the key.

Implementation EcdsaSecp256k1#

silk
impl EcdsaSecp256k1

Associated function verify#

silk
pub fn verify<H: Digest>(publicKey: &[u8], message: &[u8], signature: &[u8]) -> Result<
    (),
    Secp256k1SignatureError,
  >

Checks that signature, in the fixed 64-byte form, is a valid signature of message, hashed with H, by publicKey.

Both high and low s values are accepted.

Errors#

  • InvalidKey if publicKey is not a valid uncompressed key.

  • InvalidEncoding if signature is not 64 bytes, or r or s is not between 1 and the order of the curve.

  • AuthenticationFailed if the signature does not match.

Gotchas#

The program traps if message is longer than H allows.

Associated function verifyDigest#

silk
pub fn verifyDigest(publicKey: &[u8], digest: &[u8], signature: &[u8]) -> Result<
    (),
    Secp256k1SignatureError,
  >

Checks that signature, in the fixed 64-byte form, is a valid signature by publicKey of a message whose digest is digest.

A digest longer than 32 bytes is cut to its first 32 bytes, as ECDSA requires.

Errors#

Associated function fromDer#

silk
pub fn fromDer(signature: &[u8]) -> Result<[u8; 64], Secp256k1SignatureError>

Returns the fixed 64-byte form of the DER signature signature.

Errors#

  • InvalidEncoding if signature is not strict DER, or r or s is not between 1 and the order of the curve.

Associated function toDer#

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

Writes the DER form of the fixed 64-byte signature signature to the start of output, and returns its length, at most 72 bytes.

It does not check the values of r and s. The bytes of output after the DER do not change.

Gotchas#

The program traps if signature is not 64 bytes, or output is shorter than the DER.

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