REST API

Overview

See more context here:

How the Armory Stack works

HTTP Request Signing

API requests are signed using a client credential (a private key) rather than using bearer tokens. This provides strong authentication and non-repudiation through the stack -- it proves where the request originated and eliminates the possibility of forged requests by the service operator (e.g. Narval).

Requests to the Vault require 2 authorization headers

  • GNAP Authorization header

    • Contains the AccessToken returned from the Authorization Request

    • header format: Authorization: GNAP access_token

  • Proof of possession of the credential the access token was issued to

    • The AccessToken is bound to the credential that initiated the request, so you must prove you hold that credential by signing the HTTP request and including the signature as a detached-jws

    • header format: Detached-jws: jws

Creating a Detached JWS

To create the Detached JWS signature for your HTTP request, follow these steps

The Typescript ArmorySDK has functions to do this transparently for you

For more information, see GNAP RFC

  1. Build the JWS Header

{
    alg: 'ES256K' | 'ES256' | 'RS256' | 'EIP191',
    kid: '0x...', // Key ID of the key being presented
    typ: 'gnap-binding-jwsd',
    htm: 'POST' | 'PUT' | 'GET',
    uri: 'https://vault.armory.narval.xyz/v1/sign', // the full url
    created: 1722461078706, // unix timestamp
    ath: '8qSuB6Rj3fP2SC3Ddm7GfzCsMel4Pknptm1oRHwXs-8' // b64url sha256 hash of access token
}

Take the accessToken returned from the Authorization Request, get the sha256 hash, then base64url encode it. This is the ath value.

  1. Canonicalize the header (ensure we have the same order) according to RFC8785 See Typescript implementation here

    1. Check if the input is a number and handle special cases for NaN and Infinity.

    2. If the input is null or not an object, convert it to a JSON string.

    3. If the object has a toJSON method, canonicalize the result of this method.

    4. If the input is an array, iterate through its elements, convert undefined or symbols to null, and canonicalize each element.

    5. If the input is an object, sort its keys, filter out undefined and symbols, and canonicalize each key-value pair.

    6. Return the canonicalized string representation of the array or object.

  2. Encode the Header with base64url

  3. Canonicalize the http request body (the entire http request body) with above steps

  4. Hash the request body with SHA256 (if no body, use an empty payload)

  5. Encode the body hash with base64url -- this is your JWS payload.

  6. Sign the JWS

    1. Sign ${encodedHeader}.${encodedPayload}

    2. Use the key and algorithm referenced in the header above

      1. Note: breaking from JWS spec, we support an EIP191 signature here, although the result should still be base64url encoded

  7. Complete the JWS ${encodedHeader}.${encodedPayload}.${signature}

See our Typescript implementation of the full process here

Authorization Request Signing

Authorization Requests to the Armory are authenticated with a signature from a client credential.

This signature is presented as a JWT.

The payload is formatted as follows

{
    requestHash: '0x...' // hex encoded (w/ 0x prefix), sha256 hash of canonical body.request
    sub: '0x...' // Key ID of the key signing
    iss: 'clientId' // clientId; matches your `x-client-id` header
    iat: 1722461078706, // unix timestamp
}
  1. Take the request field of the /authorization-request body. POST /authorization-requests. Hex encode the sha256 hash of the canonical request field, and include a 0x prefix on the hex string.

    1. Note: this is not the entire body, just the request field.

  2. Build the JWT header

{
  alg: 'ES256K' | 'ES256' | 'RS256' | 'EIP191',
  kid: key.kid,
  typ: 'JWT'
}
  1. Sign the JWT

    1. Base64Url encode the canonical header

    2. Base64Url encode the canonical payload

    3. Sign ${encodedHeader}.${encodedPayload} using the key & algorithm referenced.

      1. As with jwsd, you can use an EIP191 signature and encode the result with base64url

    4. Complete the JWS ${encodedHeader}.${encodedPayload}.${signature}

  2. This value goes into the authentication field of the AuthorizationRequest (or the approvals field if you're signing an additional approval.

Last updated