> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dfns.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Generate a key pair

> Generate a cryptographic key pair for a Dfns service account or signing credential, including supported curves, formats, and tooling.

Dfns supports the following asymmetric key algorithms for credentials:

* ECDSA
* EDDSA
* RSA

Dfns supports many different curves. You can use any curve that is supported by Node JS' crypto library.

Dfns recommends the following curves / modulus lengths:

| Algorithm | Curve / Length                    |
| --------- | --------------------------------- |
| ECDSA     | secp256r1 (prime256v1 in OpenSSL) |
| EDDSA     | Ed25519                           |
| RSA       | 3072 bits                         |

### Examples

<Tabs>
  <Tab title="OpenSSL CLI">
    ```shell expandable lines theme={null}
    # Recommended: Generate a EDDSA Private Key
    # NOTE: This is not the key for the blockchain, only for the API.
    # NOTE: EDDSA keys do not work in Postman!
    openssl genpkey -algorithm Ed25519 -out ed25519key.pem
    # Generate the Public Key
    openssl pkey -in ed25519key.pem -pubout -out ed25519key.pub.pem

    #OR 

    # Generate a ECDSA Private Key
    # NOTE: This is not the key for the blockchain, only for the API.
    openssl ecparam -genkey -name prime256v1 -noout -out prime256v1.pem
    # Generate the Public Key
    openssl pkey -in prime256v1.pem -pubout -out prime256v1.pub.pem

    #OR 

    # Generate RSA Private Key
    openssl genrsa -out rsa3072.pem 3072
    # Generate the Public Key
    openssl pkey -in rsa3072.pem -pubout -out rsa3072.pub.pem
    ```

    On MacOS you may need to update your openssl version to add support for EDDSA keys

    ```shell lines theme={null}
    brew update
    brew install openssl@1.1
    echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.bash_profile
    ```
  </Tab>

  <Tab title="Node.js Crypto">
    ```typescript crypto.ts expandable lines theme={null}
    import crypto from 'crypto'

    // EDDSA Key
    const eddsaKey = crypto.generateKeyPairSync('ed25519')
    const eddsaPublicKey: string = eddsaKey.publicKey.export({ type: 'spki', format: 'pem' })
    const eddsaPrivateKey: string = eddsaKey.privateKey.export({ type: 'pkcs8', format: 'pem' })

    // ECDSA Key
    const ecdsaKey: crypto.KeyPairKeyObjectResult = crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' })
    const ecdsaPublicKey: string = ecdsaKey.publicKey.export({ type: 'spki', format: 'pem' })
    const ecdsaPrivateKey: string = ecdsaKey.privateKey.export({ type: 'pkcs8', format: 'pem' })

    // RSA Key
    const rsaKey: crypto.KeyPairKeyObjectResult = crypto.generateKeyPairSync('rsa', { modulusLength: 3072 })
    const rsaPublicKey: string = rsaKey.publicKey.export({ type: 'pkcs1', format: 'pem' })
    const rsaPrivateKey: string = rsaKey.privateKey.export({ type: 'pkcs1', format: 'pem' })
    ```
  </Tab>

  <Tab title="Web Crypto">
    ```javascript crypto.js lines expandable theme={null}
    // Code taken from https://github.com/mdn/dom-examples/blob/main/web-crypto/export-key/pkcs8.js
    function ab2str(buf) {
      return String.fromCharCode.apply(null, new Uint8Array(buf));
    }

    async function exportPrivateKey(key) {
      const exported = await window.crypto.subtle.exportKey('pkcs8', key)
      const exportedAsString = ab2str(exported)
      const exportedAsBase64 = window.btoa(exportedAsString)
      return `-----BEGIN PRIVATE KEY-----\n${exportedAsBase64}\n-----END PRIVATE KEY-----`
    }

    async function exportPublicKey(key, format) {
      const exported = await window.crypto.subtle.exportKey('spki', key)
      const exportedAsString = ab2str(exported)
      const exportedAsBase64 = window.btoa(exportedAsString)
      return `-----BEGIN PUBLIC KEY-----\n${exportedAsBase64}\n-----END PUBLIC KEY-----`
    }

    // EDDSA Key
    /*
      EDDSA support in Web Crypto is experimental and may not be present in all browsers
      See: https://nodejs.org/api/webcrypto.html#ed25519ed448x25519x448-key-pairs
    */
    const eddsaKey = await window.crypto.subtle.generateKey({ name: 'Ed25519' }, true, ['sign', 'verify'])
    const eddsaPublicKey = await exportPublicKey(eddsaKey)
    const eddsaPrivateKey = await exportPrivateKey(eddsaKey)

    // ECDSA Key
    const ecdsaKey = await window.crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify'])
    const ecdsaPublicKey = await exportPublicKey(ecdsaKey)
    const ecdsaPrivateKey = await exportPrivateKey(ecdsaKey)

    // RSA Key
    const rsaKey = await window.crypto.subtle.generateKey(
      {
        name: 'RSA-PSS',
        modulusLength: 3072,
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: "SHA-256",
      },
      true,
      ['sign', 'verify']
    )
    const rsaPublicKey = await exportPublicKey(rsaKey)
    const rsaPrivateKey = await exportPrivateKey(rsaKey)
    ```
  </Tab>

  <Tab title="Python">
    ```python crypto.py theme={null}
    from cryptography.hazmat.primitives import serialization
    from cryptography.hazmat.primitives.asymmetric import ec, ed25519, rsa

    # EDDSA Key (Ed25519)
    eddsa_private_key = ed25519.Ed25519PrivateKey.generate()
    eddsa_public_key_pem = eddsa_private_key.public_key().public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    ).decode()
    eddsa_private_key_pem = eddsa_private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
    ).decode()

    # ECDSA Key (P-256)
    ecdsa_private_key = ec.generate_private_key(ec.SECP256R1())
    ecdsa_public_key_pem = ecdsa_private_key.public_key().public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    ).decode()
    ecdsa_private_key_pem = ecdsa_private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
    ).decode()

    # RSA Key (3072 bits)
    rsa_private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=3072
    )
    rsa_public_key_pem = rsa_private_key.public_key().public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    ).decode()
    rsa_private_key_pem = rsa_private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
    ).decode()
    ```
  </Tab>

  <Tab title="AWS CLI">
    ```shell theme={null}
    # Generate a ECDSA Private Key
    aws kms create-key --key-spec ECC_NIST_P256 --key-usage SIGN_VERIFY
    # Get the Public Key
    aws kms get-public-key --key-id <KEY_ID>

    # Generate a RSA Private Key
    aws kms create-key --key-spec RSA_3072 --key-usage SIGN_VERIFY
    # Get the Public Key
    aws kms get-public-key --key-id <KEY_ID>
    ```
  </Tab>
</Tabs>

## Signature Format

An ECDSA (which includes EDDSA) signature consists of two values; a `r` and a `s`. Different crypto libraries use different formats for encoding these two values. The two most popular formats are `ASN.1 / DER format` and `raw format`.

Dfns APIs expects ECDSA/EDDSA signatures to use the `ASN.1 / DER` format.

### Raw Signatures

With `raw`, the `r` and `s` values are directly concatenated together to form the signature. Each value must be exactly 32 bytes long, with 0s added to the beginning of the `r/s` if it is less then 32 bytes.

### ASN.1 / DER

With `ASN.1 / DER`, the values are encoded using a deterministic format. The first byte is a magic value (`0x30`) that is used to identify the encoding. The second byte is the remaining length of the signature. The remaining bytes are the encoded `r` and `s` values. Each one is formated with its first byte being a magic value / separator (`0x02`). The second byte being the length of the value. And the remaining bytes being the value as a `minimal-sized signed big-endian hex number`. This means the number has all leading zeros removed, then the first byte must be a positive number (`0x00`-`0x7F`). If the minimized number starts with a negative value (`0x80`-`0xFF`) a zero is added to the beginning of the number. This means the final length of the value could be anywhere between 1 bytes and 33 bytes.

### Converting from Raw to ASN.1

If your encryption library uses `raw format` you can convert it to `ASN.1 DER` using the following code:

<CodeGroup>
  ```javascript JavaScript theme={null}
  function minimizeBigInt(value) {
    if (value.length === 0) {
      return value
    }
    const minValue = [0, ...value]
    for (let i = 0; i < minValue.length; ++i) {
      if (minValue[i] === 0) {
        continue
      }
      if (minValue[i] > 0x7f) {
        return minValue.slice(i-1)
      }
      return minValue.slice(i)
    }
    return new Uint8Array([0])
  }

  function rawSignatureToAns1(rawSignature) {
    if (rawSignature.length !== 64) {
      console.log(rawSignature.length)
      return new Uint8Array([0])
    }
    const r = rawSignature.slice(0, 32)
    const s = rawSignature.slice(32)

    const minR = minimizeBigInt(r)
    const minS = minimizeBigInt(s)

    return new Uint8Array([
      0x30,
      minR.length + minS.length + 4,
      0x02,
      minR.length,
      ...minR,
      0x02,
      minS.length,
      ...minS
    ])
  }
  ```

  ```python Python theme={null}
  from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature

  def raw_signature_to_asn1(raw_signature: bytes) -> bytes:
      """Convert a raw 64-byte signature to ASN.1 DER format."""
      if len(raw_signature) != 64:
          raise ValueError(f"Expected 64 bytes, got {len(raw_signature)}")

      r = int.from_bytes(raw_signature[:32], byteorder='big')
      s = int.from_bytes(raw_signature[32:], byteorder='big')

      # encode_dss_signature returns the ASN.1 DER encoded signature
      return encode_dss_signature(r, s)
  ```
</CodeGroup>

## Base64 and Base64Url Encoding

Base64 is a popular way of encoding large values into a string that contains only printable characters.

Base64Url is an extension of Base64 encoding, but replaces characters that are not safe in URLs with different characters. It also minimizes the string, removing the extra padding characters. The Dfns auth system leverages Base64Url encoding in many different places.

In Node.js, `Buffer` has built-in support for Base64Url as an encoding type. In Python, use the `base64` module.

<CodeGroup>
  ```javascript JavaScript (Node.js) theme={null}
  // Convert a string to a base64url string
  Buffer.from('somerandomvalue').toString('base64url')
  // Convert a base64url string to a string
  Buffer.from('c29tZXJhbmRvbXZhbHVl', 'base64url').toString()
  ```

  ```python Python theme={null}
  import base64

  # Convert a string to a base64url string
  def to_base64url(data: bytes) -> str:
      return base64.urlsafe_b64encode(data).rstrip(b'=').decode()

  # Convert a base64url string to bytes
  def from_base64url(data: str) -> bytes:
      # Add padding if needed
      padding = 4 - len(data) % 4
      if padding != 4:
          data += '=' * padding
      return base64.urlsafe_b64decode(data)

  # Example usage
  to_base64url(b'somerandomvalue')  # 'c29tZXJhbmRvbXZhbHVl'
  from_base64url('c29tZXJhbmRvbXZhbHVl').decode()  # 'somerandomvalue'
  ```
</CodeGroup>

For places where `Buffer` is not available in JavaScript, or an older `Buffer` implementation is used, this code can be used to convert a value to a Base64Url encoded string:

```javascript theme={null}
function arrayBufferToBase64(buffer) {
  const bytes = new Uint8Array(buffer)
  return btoa(String.fromCharCode(...bytes))
}

function arrayBufferToBase64Url(buffer) {
  return arrayBufferToBase64(buffer)
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
}
```

<Warning>
  **Do not double-encode signatures.** A common mistake is passing a base64 string to an encoding function like `base64url()`. These functions encode *data*, they don't convert between formats.

  ```javascript theme={null}
  // ❌ WRONG - double-encodes the signature
  function arrayBufferToBase64Url(buffer) {
    return base64url(arrayBufferToBase64(buffer))
  }

  // ✅ CORRECT - character replacement only
  function arrayBufferToBase64Url(buffer) {
    return arrayBufferToBase64(buffer)
      .replace(/=/g, '')
      .replace(/\+/g, '-')
      .replace(/\//g, '_')
  }
  ```

  Double-encoding causes `Invalid signature` errors. To diagnose: decode your signature with `echo "<signature>" | base64 -D`. If the output looks like another base64 string (e.g., `MEYCIQCDsx...`) instead of binary data, it's double-encoded.
</Warning>
