Overview

You can take steps to provide assurances and transparency to your clients and end users by verifying proof of reserves (PoR). Providing PoR is especially important for cryptocurrency exchanges and trading platforms, as it enables you to prove liquidity and solvency.

BitGo offers multiple REST APIs that you can use to return your balances and other wallet details. In its most basic form, PoR entails sharing your balances and public addresses. Clients and end users can then confirm your balances on their respective blockchains. If you require more in depth PoR, you can partner with third-party auditors. Using a Merkle tree, auditors can anonymize data and confirm ownership of individuals' assets within your wallets.

You can verify PoR for custody and self-custody wallets. However, you can't verify PoR for Go Accounts, due to the off-chain ledger they use.

Prerequisites

Steps

1. List Wallets

Get a list of your wallets, and determine which among these constitute your reserves. BitGo recommends that you use custody wallets for your reserves, as these provide maximum security.

Endpoint: List wallets

export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"

curl -X GET \
  https://app.bitgo-test.com/api/v2/wallets \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Step Result

Save the id for the wallets that make up your reserves.

{
    "wallets": [
        {
            "id": "62f0418088859b0007ad2c1337e402f0",
            "users": [
                {
                    "user": "62ab90e06dfda30007974f0a52a12995",
                    "permissions": [
                        "admin",
                        "view",
                        "spend"
                    ]
                }
            ],
            "coin": "btc",
            "label": "Cold BTC Wallet",
            "m": 2,
            "n": 3,
            "keys": [
                "62f04084d540700007a1da45e6a4d28e",
                "62f040cce56ef50007af49c09cafaf90",
                "62f040e05eec400007bd55f532d86fac"
            ],
            "keySignatures": {},
            "enterprise": "62c5ae8174ac860007aff138a2d74df7",
            "tags": [
                "62f0418088859b0007ad2c1337e402f0",
                "62c5ae8174ac860007aff138a2d74df7"
            ],
            "disableTransactionNotifications": false,
            "freeze": {},
            "deleted": false,
            "approvalsRequired": 1,
            "isCold": true,
            "coinSpecific": {},
            "admin": {},
            "clientFlags": [],
            "walletFlags": [],
            "allowBackupKeySigning": false,
            "startDate": "2022-08-07T22:49:36.000Z",
            "type": "cold",
            "buildDefaults": {},
            "customChangeKeySignatures": {},
            "hasLargeNumberOfAddresses": false,
            "multisigType": "onchain",
            "config": {},
            "balance": 1256709,
            "confirmedBalance": 1256709,
            "spendableBalance": 1256709,
            "balanceString": "1256709",
            "confirmedBalanceString": "1256709",
            "spendableBalanceString": "1256709"
        },
        {
            "id": "62f03dee8d13be0007894b3dbc21be17",
            "users": [
                {
                    "user": "62ab90e06dfda30007974f0a52a12995",
                    "permissions": [
                        "admin",
                        "view",
                        "spend"
                    ]
                }
            ],
            "coin": "dot",
            "label": "MPC DOT Wallet",
            "m": 2,
            "n": 3,
            "keys": [
                "62f03dee8d13be0007894b41f63bc504",
                "62f03dee8d13be0007894b43cb0f65d0",
                "62f03dee8d13be0007894b3f8024e0e8"
            ],
            "keySignatures": {},
            "enterprise": "62c5ae8174ac860007aff138a2d74df7",
            "tags": [
                "62f03dee8d13be0007894b3dbc21be17",
                "62c5ae8174ac860007aff138a2d74df7"
            ],
            "disableTransactionNotifications": false,
            "freeze": {},
            "deleted": false,
            "approvalsRequired": 1,
            "isCold": true,
            "coinSpecific": {
                "pendingChainInitialization": false,
                "rootAddress": "5G4oQ1xMu5V4y9Cdp9zPnVR57Nh8vsbYRRTei7MmQU6e1DHv",
                "lastNonce": 0,
                "lastChainIndex": {
                    "0": 1,
                    "1": -1
                }
            },
            "admin": {},
            "clientFlags": [],
            "walletFlags": [],
            "allowBackupKeySigning": false,
            "startDate": "2022-08-07T22:34:22.000Z",
            "type": "custodial",
            "buildDefaults": {},
            "customChangeKeySignatures": {},
            "hasLargeNumberOfAddresses": false,
            "multisigType": "MPC",
            "config": {},
            "balanceString": "2000000000000",
            "confirmedBalanceString": "2000000000000",
            "spendableBalanceString": "0"
        },
        {
            "id": "62c75ed042852800082440d8d63a4319",
            "users": [
                {
                    "user": "62ab90e06dfda30007974f0a52a12995",
                    "permissions": [
                        "admin",
                        "view",
                        "spend"
                    ]
                }
            ],
            "coin": "btc",
            "label": "Hot BTC Wallet",
            "m": 2,
            "n": 3,
            "keys": [
                "62c75ecf871f420008f23497a2ea1174",
                "62c75ecfd2d04f000858d79f4ee278ae",
                "62c75ecf3f7ede0007f9b398aafdf07b"
            ],
            "keySignatures": {
                "backupPub": "209faade005392cf930a2325db3dfba92cfa90fb27971c2b842768059f9caf653a1b76d893c3442b83549c82b8c97580948d3208d4b6e81e49c02dbb7648db1d99",
                "bitgoPub": "2089747b9eee580371691db42a85a7f3b93fd1b8be30dacdcfe537e5b48291b1462ea34afbbbc26ba11dbb5f2077843915ea812c1a6268b89d9977511563be241e"
            },
            "enterprise": "62c5ae8174ac860007aff138a2d74df7",
            "tags": [
                "62c75ed042852800082440d8d63a4319",
                "62c5ae8174ac860007aff138a2d74df7"
            ],
            "disableTransactionNotifications": false,
            "freeze": {},
            "deleted": false,
            "approvalsRequired": 1,
            "isCold": false,
            "coinSpecific": {},
            "admin": {},
            "clientFlags": [],
            "walletFlags": [],
            "allowBackupKeySigning": false,
            "startDate": "2022-07-07T22:31:44.000Z",
            "type": "hot",
            "buildDefaults": {},
            "customChangeKeySignatures": {},
            "hasLargeNumberOfAddresses": false,
            "multisigType": "onchain",
            "config": {},
            "balance": 232687,
            "confirmedBalance": 232687,
            "spendableBalance": 232687,
            "balanceString": "232687",
            "confirmedBalanceString": "232687",
            "spendableBalanceString": "232687"
        },
        {
            "id": "62c60db8b19ca800088f8ed53eb2e628",
            "users": [
                {
                    "user": "62ab90e06dfda30007974f0a52a12995",
                    "permissions": [
                        "admin",
                        "view",
                        "spend"
                    ]
                }
            ],
            "coin": "xrp",
            "label": "Cold XRP Wallet",
            "m": 2,
            "n": 3,
            "keys": [
                "62c60db8b19ca800088f8edd5ae4d4e7",
                "62c60db8b19ca800088f8ee378424d5f",
                "62c60db9b19ca800088f8f5f7ec220db"
            ],
            "keySignatures": {},
            "enterprise": "62c5ae8174ac860007aff138a2d74df7",
            "tags": [
                "62c60db8b19ca800088f8ed53eb2e628",
                "62c5ae8174ac860007aff138a2d74df7"
            ],
            "disableTransactionNotifications": false,
            "freeze": {},
            "deleted": false,
            "approvalsRequired": 1,
            "isCold": true,
            "coinSpecific": {
                "rootAddress": "rM2ESyPZwycfRwQvNo8MHKi5hAXZgjpj1X",
                "pendingChainInitialization": false,
                "creationFailure": []
            },
            "admin": {},
            "clientFlags": [],
            "walletFlags": [],
            "allowBackupKeySigning": false,
            "startDate": "2022-07-06T22:33:28.000Z",
            "type": "custodial",
            "buildDefaults": {},
            "customChangeKeySignatures": {},
            "hasLargeNumberOfAddresses": false,
            "multisigType": "onchain",
            "config": {},
            "balanceString": "12000000",
            "confirmedBalanceString": "12000000",
            "spendableBalanceString": "0"
        },
        {
            "id": "62c5b5c9d0ece30007cec9616ff29edc",
            "users": [
                {
                    "user": "62ab90e06dfda30007974f0a52a12995",
                    "permissions": [
                        "admin",
                        "view",
                        "spend"
                    ]
                }
            ],
            "coin": "btc",
            "label": "My Demo BTC Wallet",
            "m": 2,
            "n": 3,
            "keys": [
                "62c5b54a8ec0290007324c0fab5337f6",
                "62c5b54a8a0c5200071e49a857e50b0e",
                "62c5b54bd0ece30007ce8377f23f264f"
            ],
            "keySignatures": {},
            "enterprise": "62c5ae8174ac860007aff138a2d74df7",
            "tags": [
                "62c5b5c9d0ece30007cec9616ff29edc",
                "62c5ae8174ac860007aff138a2d74df7"
            ],
            "disableTransactionNotifications": false,
            "freeze": {},
            "deleted": false,
            "approvalsRequired": 1,
            "isCold": false,
            "coinSpecific": {},
            "admin": {},
            "clientFlags": [],
            "walletFlags": [],
            "allowBackupKeySigning": false,
            "startDate": "2022-07-06T16:18:17.000Z",
            "type": "hot",
            "buildDefaults": {},
            "customChangeKeySignatures": {},
            "hasLargeNumberOfAddresses": false,
            "multisigType": "onchain",
            "config": {},
            "balance": 1089773,
            "confirmedBalance": 1089773,
            "spendableBalance": 1089773,
            "balanceString": "1089773",
            "confirmedBalanceString": "1089773",
            "spendableBalanceString": "1089773"
        },
    ]
}

2. List Addresses

Using the wallet ids, get all the addresses for your reserve wallets.

Endpoint: List addresses

export COIN="<ASSET_ID>"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"

curl -X GET \
  "https://app.bitgo-test.com/api/v2/$COIN/wallet/$WALLET_ID/addresses?includeBalances=true" \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Step Result

Save each address that contains a balance. Anyone can search for these public addresses on their respective blockchains.

{
    "coin": "btc",
    "totalAddressCount": 3,
    "addresses": [
        {
            "id": "62f0418088859b0007ad2c23dd2a316e",
            "address": "2MxNxcyy4Jt4NQGkwR8bVD6pYPDSMhQqkmr",
            "chain": 10,
            "index": 1,
            "coin": "btc",
            "wallet": "62f0418088859b0007ad2c1337e402f0",
            "coinSpecific": {
                "redeemScript": "00201ca0444be0e22067cf8ed6cbc81268ade365e0559844d00eedf3b0af22e9bfc0",
                "witnessScript": "5221020292b2ee9c27e3d1425b8516ebd43351b2d163b7dfb839a36b9c73e40afe043021029b387b9a41acbcf0f6c8c0fd72247a0670dcc7bcf0e0c19d466253b6e8b6cbb821023f3e4bec6ff7d805f168e7ce80a813c3f0df26f4b29e1dea3bbeda90291d1e8f53ae"
            },
            "balance": {
                "updated": "2022-08-31T16:07:28.000Z",
                "balance": 1256709,
                "balanceString": "1256709",
                "totalReceived": 1256709,
                "totalSent": 0,
                "confirmedBalanceString": "1256709",
                "spendableBalanceString": "1256709",
                "tokens": {}
            }
        },
        {
            "id": "630f8734f2d4160007837e4c34a1a9b2",
            "address": "2N3oMXAzeU6XBBBFmWJiFDbwtVxRiUprsv6",
            "chain": 10,
            "index": 2,
            "coin": "btc",
            "wallet": "62f0418088859b0007ad2c1337e402f0",
            "coinSpecific": {
                "redeemScript": "0020a9f3c4d917a9094193fe8baaf88844ff42ea96de1a1fee92fdc9e84093794395",
                "witnessScript": "5221022c9f415e4c7491fb6f87dad25294f6e083bd9511bf50f94e031e0d387740f7c121035da85278e3d4dc17100b4fb1f591d552d64e74501a7b9927f69e9385f82946fe2103b569f313c45d577f56f38efb163d4d86d32b2281f4d605a2eb4e6102cc118fbc53ae"
            },
            "balance": {
                "balance": 0,
                "balanceString": "0",
                "totalReceived": 0,
                "totalSent": 0,
                "confirmedBalanceString": "0",
                "spendableBalanceString": "0",
                "tokens": {}
            }
        },
        {
            "id": "630fc9fe7db3fd0007157e7b0867d82b",
            "address": "tb1qkzkvluehktkcrx6y85yjlqyal558j9nk4garzghyfjtkwyw73prsvvzcwf",
            "chain": 21,
            "index": 1,
            "coin": "btc",
            "wallet": "62f0418088859b0007ad2c1337e402f0",
            "coinSpecific": {
                "witnessScript": "52210319a0ad3dda87215d90a195e074f0f053d393cc55dd43c742e2d59bca9c8f495921033ebb11af652e887e17be2b8e029b792318426e136439d0cbb0a86d3645617620210319128393716221430d05a29930e75cabaf9fae741f0c0882030fb26b2e42c2b953ae"
            },
            "balance": {
                "balance": 0,
                "balanceString": "0",
                "totalReceived": 0,
                "totalSent": 0,
                "confirmedBalanceString": "0",
                "spendableBalanceString": "0",
                "tokens": {}
            }
        }
    ],
    "count": 3
}

Repeat this step for each wallet in your reserve.

3. Confirm Address Ownership

For multisignature wallets ("multisigType": "onchain"), you can verify address ownership by proving your keys belong to the address. There are two different methods for verifying address ownership. Choose the method you prefer.

Re-derive the wallet address from the public keys of the wallet and confirm it matches the address BitGo returned. Because the derivation uses all three wallet keys (user, backup, and BitGo), a successful match proves those keys constructed the address and that you own it.

Send the public keys of the wallet in the keychains array, along with the address and the derivation index from the Step 2 address record. You can get each public key with Get Key, using the key IDs from Step 1. This method doesn't support custodial wallets.

Endpoint: Verify if an address belongs to a wallet

export BITGO_EXPRESS_HOST="<YOUR_LOCAL_HOST>"
export COIN="<ASSET_ID>"
export WALLET_ID="<YOUR_WALLET_ID>"
export ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
export ADDRESS="<ADDRESS_TO_VERIFY>"
export INDEX="<ADDRESS_INDEX>"

curl -X POST \
  http://$BITGO_EXPRESS_HOST/api/v2/$COIN/wallet/$WALLET_ID/iswalletaddress \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "address": "'"$ADDRESS"'",
    "index": '"$INDEX"',
    "keychains": [
      { "pub": "<USER_PUBLIC_KEY>" },
      { "pub": "<BACKUP_PUBLIC_KEY>" },
      { "pub": "<BITGO_PUBLIC_KEY>" }
    ]
  }'

Step Result

The endpoint returns true when the public keys of the wallet derive the address, which proves you own it. If it returns false, confirm that the index matches the Step 2 address record and that all three public keys are correct.

true

Note: Forwarder version 0 addresses always return true, because the architecture prevents a cryptographic check.

4. Aggregate Balances

Write a script that aggregates the balances for your coin or tokens. Ensure that you're adding the balances of every address in your reserve wallets.

5. Share Balances

Once you aggregate your balances, you can share your reserves, along with the public addresses. Your clients and end users can search for the public addresses on their respective blockchains and confirm their balances.

If you need to confirm individuals' assets within your reserves, you can share your PoR with a third-party auditor.

See Also