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

# Verifying addresses

> Verify deposit address authenticity and integrity using Anchorage Digital's V1 and V2 signature schemes.

The addresses REST API endpoints return signatures of the address strings and other metadata that prove the address was generated by Anchorage Digital for your organization.

<Warning>
  Verify the address signature and all accompanying metadata before using any address. This confirms authenticity and integrity.
</Warning>

## Signature schemes

The API supports two address verification schemes. Check the `signatureVersion` field to determine which applies.

| Scheme | How it works                                                                                                                                                                                                                      |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **V1** | Verify signature against a fixed Ed25519 public key unique to your organization, distributed out-of-band by Anchorage Digital. Must be kept tamper-proof.                                                                         |
| **V2** | Verify signature against the public key of the leaf certificate in an X.509 certificate chain returned with the response, then verify the chain against the Anchorage Digital Address Signing Root CA (hard-coded by the client). |

***

## V1 address signature verification

### Steps

1. **Verify the signature:**
   * Decode `addressSignaturePayload` from hex to bytes.
   * Decode `signature` from hex to bytes.
   * Using your organization's fixed public key, verify that `signatureBytes` is a valid Ed25519 signature of `addressSignaturePayloadBytes`.

2. **Verify the signed address matches the address to be used:**
   * Decode `addressSignaturePayload` from hex to bytes.
   * Parse the bytes as JSON.
   * Verify the address matches the `TextAddress` property in the JSON.

<Warning>
  Validating the signature alone is insufficient. You must also confirm that `TextAddress` in the decoded payload matches the address you intend to use.
</Warning>

### V1 signed payload fields

| Field         | Description                              |
| ------------- | ---------------------------------------- |
| `TextAddress` | The text format of the on-chain address. |

```json theme={null}
{
  "TextAddress": "2N19AcihQ1a4MxQW658UFHTioUNnMkiHPkw"
}
```

### Sample V1 validation code

```go theme={null}
package main

import (
    "crypto/ed25519"
    "encoding/hex"
    "encoding/json"
    "fmt"
)

// V1SignedPayload represents the JSON structure in the addressSignaturePayload for V1 signatures
type V1SignedPayload struct {
    TextAddress string `json:"TextAddress"`
}

// verifyV1AddressSignature verifies a V1 address signature.
//
// Parameters:
//   - address: The address string from the API response
//   - addressSignaturePayload: Hex-encoded bytes that were signed
//   - signature: Hex-encoded Ed25519 signature
//   - orgPublicKeyHex: Hex-encoded Ed25519 public key for your organization (obtained out-of-band)
//
// Returns an error if verification fails.
func verifyV1AddressSignature(address, addressSignaturePayload, signature, orgPublicKeyHex string) error {
    // Step 1: Check the validity of the signature

    // Decode the addressSignaturePayload from hex to bytes
    payloadBytes, err := hex.DecodeString(addressSignaturePayload)
    if err != nil {
        return fmt.Errorf("failed to decode addressSignaturePayload: %w", err)
    }

    // Decode the signature from hex to bytes
    signatureBytes, err := hex.DecodeString(signature)
    if err != nil {
        return fmt.Errorf("failed to decode signature: %w", err)
    }

    // Decode the organization public key from hex
    publicKeyBytes, err := hex.DecodeString(orgPublicKeyHex)
    if err != nil {
        return fmt.Errorf("failed to decode organization public key: %w", err)
    }

    if len(publicKeyBytes) != ed25519.PublicKeySize {
        return fmt.Errorf("invalid public key size: got %d bytes, expected %d", len(publicKeyBytes), ed25519.PublicKeySize)
    }

    publicKey := ed25519.PublicKey(publicKeyBytes)

    // Verify the Ed25519 signature
    if !ed25519.Verify(publicKey, payloadBytes, signatureBytes) {
        return fmt.Errorf("signature verification failed")
    }

    // Step 2: Verify the signed address matches the address to be used

    // Parse the payload bytes as JSON
    var signedPayload V1SignedPayload
    if err := json.Unmarshal(payloadBytes, &signedPayload); err != nil {
        return fmt.Errorf("failed to parse signed payload: %w", err)
    }

    // Verify the TextAddress matches
    if signedPayload.TextAddress != address {
        return fmt.Errorf("signed TextAddress does not match: signed=%q, expected=%q", signedPayload.TextAddress, address)
    }

    return nil
}

func main() {
    // Sample API response data
    address := "2N19AcihQ1a4MxQW658UFHTioUNnMkiHPkw"
    addressSignaturePayload := "7b225465787441646472657373223a22324e313941636968513161344d78515736353855464854696f554e6e4d6b6948506b77227d"
    signature := "b18f6848dc0fef01a069e7ac26046383bf5cd130203994dc2d72b5a9097351b1e8b67115b63124fbc8c16673566416a635913c670b676089339c62a7824baa03"

    // Organization public key - obtained out-of-band from Anchorage Digital beforehand
    // Unique per Organization, fixed for the lifetime of that Organization
    // Must be kept tamper-proof
    orgPublicKeyHex := "8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c"

    // Verify the signature
    if err := verifyV1AddressSignature(address, addressSignaturePayload, signature, orgPublicKeyHex); err != nil {
        fmt.Printf("✗ V1 Address signature verification failed: %v\n", err)
        return
    }

    fmt.Println("✓ V1 Address signature verified successfully!")
    fmt.Printf("  Address: %s\n", address)
    fmt.Println("\nYou may now safely use this address for deposits.")
}
```

***

## V2 address signature verification

Addresses with `signatureVersion: V2` include a `certChain` field containing an X.509 certificate chain in PEM format.

### Steps

1. **Verify the certificate chain:**

   * Parse `certChain` as PEM-encoded X.509 certificates.
   * Verify the chain from the leaf to the trusted Anchorage Digital Root CA.
   * Verify all certificates are temporally valid (both `notAfter` and `notBefore`).
   * Verify the leaf certificate's Subject Alternative Names include `address-provider.anchorage.internal`.
   * Verify the leaf certificate's `KeyUsage` includes both `digitalSignature` and `nonRepudiation` (also known as `contentCommitment`).
   * Extract the public key from the leaf certificate. Currently only Ed25519 keys are supported, but this may change.

   <Note>
     The leaf certificate is at index 0, followed by zero or more intermediates. The root cert is excluded from the response. The number of certificates in the chain may change. Follow standard X.509 verification procedures — not all libraries perform all checks by default.
   </Note>

2. **Verify the signature:**
   * Decode `addressSignaturePayload` from hex to bytes.
   * Decode `signature` from hex to bytes.
   * Using the leaf certificate's public key, verify `signatureBytes` is a valid signature of the payload bytes.

3. **Verify the signed details:**

   * Parse `addressSignaturePayload` bytes as JSON.
   * Verify `SignatureExpiresAt` ≥ current UTC Unix timestamp.
   * Verify `TextAddress` matches the address to be used.
   * Verify `VaultId` matches your expected Vault ID.
   * Verify `NetworkId` matches the expected network for this address.

   <Note>
     `NetworkName` is included for human readability and does not need to be verified. Do not use a "strict" JSON parser that rejects extra properties — future versions may add fields.
   </Note>

   <Note>
     Anchorage Digital periodically refreshes V2 signatures and the Address Signing Root CA before expiration. The deposit address itself will not change — only the signature, certificate chain, and Root CA are updated.
   </Note>

### V2 signed payload fields

| Field                | Description                                                   |
| -------------------- | ------------------------------------------------------------- |
| `TextAddress`        | The text format of the on-chain address.                      |
| `VaultId`            | Identifies the vault this address belongs to.                 |
| `NetworkId`          | Identifies the network this address can receive deposits on.  |
| `NetworkName`        | Human-readable version of `NetworkId`.                        |
| `SignatureExpiresAt` | Unix timestamp after which the signature must not be trusted. |

```json theme={null}
{
  "VaultId": "dae6089e7c0836705f0562af0f1e4e1f",
  "TextAddress": "bcrt1q709skemgf5skpsnysvgme2s3ztehkutl390yl0wp29lnmum5uw7qg0qrwm",
  "NetworkName": "Bitcoin Regnet",
  "NetworkId": "BTC_R",
  "SignatureExpiresAt": 1769450713
}
```

### Anchorage Digital Address Signing Root CAs

Hard-code the appropriate Root CA for the environment you are targeting. This value must be tamper-proof.

<Tabs>
  <Tab title="Production">
    ```
    -----BEGIN CERTIFICATE-----
    MIIBXTCCAQ+gAwIBAgIUQZI+MSvYTXQHra+3OAKnwAMzotUwBQYDK2VwMCAxHjAc
    BgNVBAMMFWNhLmFuY2hvcmFnZS5pbnRlcm5hbDAeFw0yNjAxMjYwMDAwMDBaFw0y
    NzAxMjYwMDAwMDBaMCAxHjAcBgNVBAMMFWNhLmFuY2hvcmFnZS5pbnRlcm5hbDAq
    MAUGAytlcAMhADTh1nctgIHtAKNW8ww/bY606pJ3OP2dyZYcQrU2kG5jo1swWTAP
    BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwICBDAUBgorBgEEAYaNHwEBBAYW
    BHJvb3QwIAYDVR0RBBkwF4IVY2EuYW5jaG9yYWdlLmludGVybmFsMAUGAytlcANB
    ANkkdudEjH9RTKbRAxrRXyMSS/TgmdSrAVYOZzoRDJlyc+5oD+a0pmmwWVe86xZi
    37YbN1GzVlXcJAPpV6ceEQU=
    -----END CERTIFICATE-----
    ```
  </Tab>

  <Tab title="Staging">
    ```
    -----BEGIN CERTIFICATE-----
    MIIBXDCCAQ6gAwIBAgITOfTQ4rYUsghgvdl8YCJSC67uGDAFBgMrZXAwIDEeMBwG
    A1UEAwwVY2EuYW5jaG9yYWdlLmludGVybmFsMB4XDTI2MDEyNDAwMDAwMFoXDTI3
    MDEyNDAwMDAwMFowIDEeMBwGA1UEAwwVY2EuYW5jaG9yYWdlLmludGVybmFsMCow
    BQYDK2VwAyEAPlBo2/+kPPL0WRpT+B/yHsU25AN/M6HP2bzC61yHb4ajWzBZMA8G
    A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgIEMBQGCisGAQQBho0fAQEEBhYE
    cm9vdDAgBgNVHREEGTAXghVjYS5hbmNob3JhZ2UuaW50ZXJuYWwwBQYDK2VwA0EA
    YsJxVI9n42liCF9f+Ou7uuC1QGFwaHwFsfOm0WFofSlE1trWqzj4ruzjPYSRJc8H
    t2A7XCAfXkG0mzKpL/wQDg==
    -----END CERTIFICATE-----
    ```
  </Tab>
</Tabs>

### Sample V2 validation code

```go theme={null}
package main

import (
    "crypto/ed25519"
    "crypto/x509"
    "encoding/hex"
    "encoding/json"
    "encoding/pem"
    "fmt"
    "time"
)

// V2SignedPayload represents the JSON structure in the addressSignaturePayload for V2 signatures
type V2SignedPayload struct {
    TextAddress        string `json:"TextAddress"`
    VaultId            string `json:"VaultId"`
    NetworkId          string `json:"NetworkId"`
    NetworkName        string `json:"NetworkName"`
    SignatureExpiresAt int64  `json:"SignatureExpiresAt"` // Unix timestamp
}

// verifyV2AddressSignature verifies a V2 address signature.
//
// Parameters:
//   - now: The "current" time. Note that conforming implementations must use
//     a trusted source for the current time.
//   - address: The address string from the API response
//   - addressSignaturePayload: Hex-encoded bytes that were signed
//   - signature: Hex-encoded signature
//   - certChainPEM: PEM-encoded certificate chain (leaf first, then intermediates)
//   - rootCAPEM: PEM-encoded Root CA certificate (hard-coded by client)
//   - expectedVaultId: Your Vault ID to verify against the signed VaultId
//   - expectedNetworkId: Expected network ID for this address (e.g., "BTC", "ETH")
//
// Returns an error if verification fails.
func verifyV2AddressSignature(
    now time.Time,
    address, addressSignaturePayload, signature, certChainPEM, rootCAPEM, expectedVaultId, expectedNetworkId string,
) error {
    // Step 1: Verify the certificate chain

    // Parse the certificate chain from PEM
    certs, err := parsePEMCertificates([]byte(certChainPEM))
    if err != nil {
        return fmt.Errorf("failed to parse certificate chain: %w", err)
    }

    if len(certs) == 0 {
        return fmt.Errorf("certificate chain is empty")
    }

    leafCert := certs[0]
    var intermediateCerts []*x509.Certificate
    if len(certs) > 1 {
        intermediateCerts = certs[1:]
    }

    // Parse the Root CA
    rootCACerts, err := parsePEMCertificates([]byte(rootCAPEM))
    if err != nil {
        return fmt.Errorf("failed to parse Root CA: %w", err)
    }
    if len(rootCACerts) != 1 {
        return fmt.Errorf("expected exactly one Root CA certificate, got %d", len(rootCACerts))
    }
    rootCA := rootCACerts[0]

    // Verify the leaf certificate's KeyUsage includes both
    // digitalSignature and nonRepudiation (AKA contentCommitment)
    if leafCert.KeyUsage&x509.KeyUsageDigitalSignature == 0 {
        return fmt.Errorf("leaf certificate KeyUsage missing DigitalSignature")
    }
    if leafCert.KeyUsage&x509.KeyUsageContentCommitment == 0 {
        return fmt.Errorf("leaf certificate KeyUsage missing NonRepudiation (ContentCommitment)")
    }

    // Verify the certificate chain from leaf to Root CA
    roots := x509.NewCertPool()
    roots.AddCert(rootCA)

    intermediates := x509.NewCertPool()
    for _, cert := range intermediateCerts {
        intermediates.AddCert(cert)
    }

    // NOTE: Not all x509 libraries are created equal and are not
    // guaranteed to verify exactly the same things!
    //
    // Always review the library you plan to use and ensure it covers the
    // checks described in the User Guide!
    //
    // For example, the Go implementation checks all Certificates for
    // temporal validity (notBefore and notAfter against CurrentTime), for
    // valid signatures up the chain, and checks that the Subject
    // Alternative Names include the values in DNSNames below.
    //
    // However it does not check the KeyUsage bits, hence the additional
    // checks above.
    opts := x509.VerifyOptions{
        DNSNames:      []string{"address-provider.anchorage.internal"},
        Roots:         roots,
        Intermediates: intermediates,
        CurrentTime:   now,
        // NOTE: This allows for any Extended Key Usage, but does not
        // check the Key Usage bits, hence the additional checks above.
        KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
    }
    if _, err := leafCert.Verify(opts); err != nil {
        return fmt.Errorf("certificate chain verification failed: %w", err)
    }

    // Extract the public key from the leaf certificate
    leafPublicKey, ok := leafCert.PublicKey.(ed25519.PublicKey)
    if !ok {
        return fmt.Errorf("leaf certificate does not use Ed25519 (got type %T)", leafCert.PublicKey)
    }

    // Step 2: Verify the signature

    // Decode the addressSignaturePayload from hex to bytes
    payloadBytes, err := hex.DecodeString(addressSignaturePayload)
    if err != nil {
        return fmt.Errorf("failed to decode addressSignaturePayload: %w", err)
    }

    // Decode the signature from hex to bytes
    signatureBytes, err := hex.DecodeString(signature)
    if err != nil {
        return fmt.Errorf("failed to decode signature: %w", err)
    }

    // Verify the signature using the leaf certificate's public key
    if !ed25519.Verify(leafPublicKey, payloadBytes, signatureBytes) {
        return fmt.Errorf("signature verification failed")
    }

    // Step 3: Verify the signed details

    // Parse the payload bytes as JSON
    var signedPayload V2SignedPayload
    if err := json.Unmarshal(payloadBytes, &signedPayload); err != nil {
        return fmt.Errorf("failed to parse signed payload: %w", err)
    }

    // Verify SignatureExpiresAt is not in the past
    if now.Unix() > signedPayload.SignatureExpiresAt {
        expiryTime := time.Unix(signedPayload.SignatureExpiresAt, 0)
        return fmt.Errorf("signature has expired at %s", expiryTime)
    }

    // Verify TextAddress matches
    if signedPayload.TextAddress != address {
        return fmt.Errorf("signed TextAddress does not match: signed=%q, expected=%q",
            signedPayload.TextAddress, address)
    }

    // Verify VaultId matches
    if signedPayload.VaultId != expectedVaultId {
        return fmt.Errorf("signed VaultId does not match: signed=%q, expected=%q",
            signedPayload.VaultId, expectedVaultId)
    }

    // Verify NetworkId matches
    if signedPayload.NetworkId != expectedNetworkId {
        return fmt.Errorf("signed NetworkId does not match: signed=%q, expected=%q",
            signedPayload.NetworkId, expectedNetworkId)
    }

    return nil
}

// parsePEMCertificates parses PEM-encoded certificates and returns them as a slice
func parsePEMCertificates(pemData []byte) ([]*x509.Certificate, error) {
    var certs []*x509.Certificate

    for {
        block, rest := pem.Decode(pemData)
        if block == nil {
            break
        }

        if block.Type != "CERTIFICATE" {
            pemData = rest
            continue
        }

        cert, err := x509.ParseCertificate(block.Bytes)
        if err != nil {
            return nil, fmt.Errorf("failed to parse certificate: %w", err)
        }

        certs = append(certs, cert)
        pemData = rest
    }

    return certs, nil
}

func main() {
    // Sample API response data
    address := "bcrt1q709skemgf5skpsnysvgme2s3ztehkutl390yl0wp29lnmum5uw7qg0qrwm"
    addressSignaturePayload := "7b225661756c744964223a226461653630383965376330383336373035663035363261663066316534653166222c225465787441646472657373223a22626372743171373039736b656d676635736b70736e797376676d653273337a7465686b75746c333930796c30777032396c6e6d756d357577377167307172776d222c224e6574776f726b4e616d65223a22426974636f696e205265676e6574222c224e6574776f726b4964223a224254435f52222c225369676e6174757265457870697265734174223a313736393435303731337d"
    signature := "951eb2fb560e660aa9c3d1ccd120d3ad1a19d90d8747347057e48bf174330eb386089e3232d822fd66b8183cce8059c91183afde299b920a0e0c05c5b167360e"
    certChainPEM := `-----BEGIN CERTIFICATE-----
MIIBYTCCAROgAwIBAgIUMLKt+K9eFku+P7BbefE1xAHg0hcwBQYDK2VwMAAwHhcN
MjYwMTI2MTcwNDEzWhcNMjcwMTI2MTcwNTEzWjAuMSwwKgYDVQQDEyNhZGRyZXNz
LXByb3ZpZGVyLmFuY2hvcmFnZS5pbnRlcm5hbDAqMAUGAytlcAMhAPsgM70aWFYs
ZaLHawtYJpl42BkiTLyCq96+OXe4FxrVo3EwbzAOBgNVHQ8BAf8EBAMCBsAwDAYD
VR0TAQH/BAIwADAfBgNVHSMEGDAWgBS0usSFeB2gjC+wcowtxN3MeKSH7zAuBgNV
HREEJzAlgiNhZGRyZXNzLXByb3ZpZGVyLmFuY2hvcmFnZS5pbnRlcm5hbDAFBgMr
ZXADQQCXmvIkuPnUgCHxWmFmzvgWdv9lUlt84oZCel+OeJW9n8PR88tGxAcD1E3+
KDBXVpO0GcRA0W9+xqqICAo2ROEJ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBKDCB26ADAgECAhRGsD05KldIse+uIEa976AijTqlxjAFBgMrZXAwADAeFw0y
NjAxMjYxNzA0MTNaFw0yNzAxMjYxNzA1MTNaMAAwKjAFBgMrZXADIQCNpyY5Sr21
FHNvvLkBKG8AEMKdhqtajmV5d2QaZlmtAqNnMGUwDgYDVR0PAQH/BAQDAgIEMBIG
A1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLS6xIV4HaCML7ByjC3E3cx4pIfv
MCAga1UdEQEB/wQWMBSCEmFuY2hvcmFnZS5pbnRlcm5hbDAFBgMrZXADQQCIgw6k
LMIwhd3ACjG03cJ5z/ZZp8aXXycFq2ZC9TLhieJ3rncyMH6ZdyJ3Ai1eVaHs4vn
DCv54Vdh83vvSky4K
-----END CERTIFICATE-----
`

    // NOTE: This is a FAKE Root CA used just for this example.
    // NOTE: Conforming client implementations should hard-code the real
    // Anchorage Digital Address Signing Root CA for the environment they
    // are making requests to.
    rootCAPEM := `-----BEGIN CERTIFICATE-----
MIIBGzCBzqADAgECAhQ2qQwArneTuF0dbNDs8i/ExuyW2DAFBgMrZXAwADAeFw0y
NjAxMjYxNzA0MTNaFw0yNzAxMjYxNzA1MTNaMAAwKjAFBgMrZXADIQB+gEnytXKn
uAMonIWGWnB0qyTqa0aw3l9u5VRbu86UgaNaMFgwDgYDVR0PAQH/BAQDAgIEMA8G
A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOj64tL1teJHkojsiblnnK34Tw+EMBYGN
A1UdEQEB/wQMMAqCCGludGVybmFsMAUGAytlcANBAAg2IcVEXmWKSivhUNSatNfM
mASxi83QscIuyP/sIW2sRIuCqQJoo9lN6TaxzyV62cQMzthFOCZcgRE+k0JV7Ao=
-----END CERTIFICATE-----
`

    // Your Vault ID - obtained from your application context
    expectedVaultId := "dae6089e7c0836705f0562af0f1e4e1f"

    // Expected network ID for this address
    expectedNetworkId := "BTC_R"

    // Implementations should use the actual current time
    // now := time.Now()
    now := time.Unix(1769450600, 0) // Fake time so that this example passes.

    // Verify the signature
    if err := verifyV2AddressSignature(
        now,
        address,
        addressSignaturePayload,
        signature,
        certChainPEM,
        rootCAPEM,
        expectedVaultId,
        expectedNetworkId,
    ); err != nil {
        fmt.Printf("✗ V2 Address signature verification failed: %v\n", err)
        return
    }

    fmt.Println("✓ V2 Address signature verified successfully!")
    fmt.Printf("  Address: %s\n", address)
    fmt.Printf("  Vault ID: %s\n", expectedVaultId)
    fmt.Printf("  Network: %s\n", expectedNetworkId)
    fmt.Println("\nYou may now safely use this address for deposits.")
}
```
