Skip to content

Instantly share code, notes, and snippets.

@rubensayshi
Last active January 15, 2023 06:24
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rubensayshi/da922774d43976e0804c to your computer and use it in GitHub Desktop.
Save rubensayshi/da922774d43976e0804c to your computer and use it in GitHub Desktop.
BlockTrail Wallet API

BlockTrail Wallet API

The BlockTrail Wallet API is a 2of3 Multi-Signature HD Wallet API that provides easy to use wallet functionality with the advantages of having a HD wallet with a secondary backup key and a blocktrail co-singing key.
While (with the SDKs) still remaining easy enough to use for any developer to integrate bitcoin functionality into their applications without any prior (technical) bitcoin knowledge.

Following is a draft of the 'spec' of how the various aspects of the wallet function under the hood.

PHP Implementation of the Client SDK: https://github.com/blocktrail/blocktrail-sdk-php
NodeJS Implementation of the Client SDK: https://github.com/blocktrail/blocktrail-sdk-nodejs/tree/wallet-api

Changelog

  • No longer using BIP44 but instead use custom BIP32 path

2of3 Multi-Signature Wallet

  • Primary Key
    • BIP32
    • (BIP39) generated by the client
    • mnemonic stored on blocktrail servers
    • requires a passprhase
    • public key stored on blocktrail servers
  • Backup Key
    • BIP32
    • (BIP39) generated by the client
    • stored securely (offline)
    • public key stored on blocktrail servers
  • BlockTrail Co-Signing Key
    • BIP32
    • seperate key generate by the server for each wallet (BIP32 derivation of master key)
    • can be 'upgraded' (automatically when using the SDK) to a new master key in case it's ever compromised see Blocktrail cosign key upgrade section

The backup key can be used incase it's not possible to have Blocktrail cosign (offline, became evil, raided, w/e). The BlockTrail Co-Singing Key will allow us to add features such as daily spending limits etc (damage controlled when hacked etc).

HD Wallet

All three keys are BIP32 keys and following our custom path format new derivations are created of each key and combined together for new addresses.

BIP32 Path

When creating child key derivations we use the following BIP32 Path: m / blocktrail_key_index' / chain / address_index

  • blocktrail_key_index' blocktrail key used for the multi-signature address creation hardened
  • chain address chain, can be used similairly to BIP44 'change' part for having a chain with a different purpose
  • address_index address sequence number

BIP32 Path - Blocktrail Key Index

The 'Blocktrail Key Index' part of the BIP32 path is used for keeping track of which blocktrail cosigning key is being used. This allows us to easily 'upgrade' a wallet to a new blocktrail cosigning key in case it ever get's compromised. It also allows us to create wallets with 'unsafe' blocktrail cosigning keys - that are on our dev and staging environment (used for tests etc).

BIP32 Path - Backup Key

The Backup Key will not harden the path, because we want to be able to derive new public keys from it without the need of the private keys. So the path used for the Backup Key is: m / blocktrail_key_index / chain / address_index

Wallet Creation

Client:
  1. input:

    • identifier for wallet
    • passprhase for wallet
  2. creates primary key

    • generates BIP39 mnemonic (512 bit entropy)
    • creates seed out of mnemonic + passphrase
    • creates BIP32 master key out of seed
  3. create backup key

    • generate BIP39 mmnemonic (512 bit entropy)
    • creates seed out of mnemonic + blank passprhase
    • creates BIP32 master key out of seed
  4. create a checksum of primary BIP32 master key

    • create 'checksum' by generating the address for the m BIP32 key
  5. create M / blocktrail_key_index' derivation of the public key

    • where the blocktrail_key_index' will initially be 0 and might be incremented when there's a need for a new blocktrail cosign key
  6. POST to API

    • send identifier
    • send public key for M / blocktrail_key_index' of the primary key
    • send public key for M of the backup key
    • send mnemonic of the primary key
    • send checksum
    • send the 'blocktrail_key_index' that is used (initially 0)
  7. receive the public key(s) of the the blocktrail co-signing key for M / blocktrail_key_index'

  8. (optional) received upgrade_key_index as response

    • send public key of M / upgrade_key_index' of the primary key where upgrade_key_index' is the requesed upgrade key index
  9. receive the public key(s) of the the blocktrail co-signing key for M / upgrade_key_index'

  10. initialize Wallet object, return primary mnemonic, backup mnemonic and blocktrail co-signing public key for offline storage

Server:
  1. store data
    • identifier
    • mnemonic
    • backup public key
    • checksum
    • blocktrail key index
  2. store primary public key for given path M / blocktrail_key_index'
  3. verify that the blocktrail_key_index used in the primary public key is the latest blocktrail co-signing key, otherwise respond with upgrade_key_index (this is part of the normal return, upgrading is optional)
  4. for all possible blocktrail_key_indexes retrieve the co-signing master key, derive m / walletId' modify the i of the BIP32 key so that it appears that this is m / key_index'
  5. return list of blocktrail public keys
  6. store encrypted backup of the above data in S3 using public key of our RSA backup key for an extra backup measure next to normal database backups (using openssl_seal)
  7. (manual operation) print the encrypted backup of the above data and store in vault for off-site backup measure

Wallet (re)initialization

Client:
  1. input:

    • identifier for wallet
    • passprhase for wallet
  2. retrieve wallet data (based on identifier) from server

    • primary mnemonic
    • backup public key
    • checksum
    • blocktrail public keys
    • blocktrail key index
  3. create BIP32 master key out of mnemonic

    • mnemonic + passprhase -> seed -> BIP32 master key
    • create 'checksum' by generating the address for the m BIP32 key
    • verify the checksum
  4. (optional) received upgrade_key_index as response

    • send public key of M / upgrade_key_index' of the primary key where upgrade_key_index' is the requesed upgrade key index
  5. receive the public key(s) of the the blocktrail co-signing key for M / upgrade_key_index'

  6. initialize Wallet object

Server:
  1. return data
    • identifier
    • mnemonic
    • backup public key
    • checksum
  2. verify that the blocktrail_key_index used in the primary public key is the latest blocktrail co-signing key, otherwise respond with upgrade_key_index (this is part of the normal return, upgrading is optional)
  3. for all possible blocktrail_key_indexes retrieve the co-signing master key, derive m / walletId' modify the i of the BIP32 key so that it appears that this is m / key_index'
  4. return list of blocktrail public keys

New Address Generation

Client:
  1. request new derivation from API
    • specify the BIP32 path of which a child should be derived (eg; M/0'/0 will give M/0'/0/232 as response)
  2. generate redeemscript and address
    • BIP32 derivation of primary (private) key with the received path
    • BIP32 derivation of backup (public) key with the received (unhardened) path
    • BIP32 derivation of blocktrail (public) key with the received path
    • sort pub keys by length and lexicographic order
Server:
  1. atomic increment of specified BIP32 path child sequence number
  2. store generated path and generated redeemscript and address (see above for logic)
  • Note that this could also be used with BIP32 paths other than the 'standard' BlockTrail format if the user ever has a desire to do so

Create/Sign/Send Transaction

Client:
  1. request coin selection from API for desired outputs, with UTXO locking enabled
  2. determine if the change and fee returned from the coin selection are sane values
  3. generate change address if needed
  4. build list of outputs
  5. create raw transaction
  6. sign raw transaction with BIP32 keys based on paths specified by coin selection
  7. send partially signed transaction to API along with the path that were used to sign the raw transaction
Server (coin selection):
  1. Acquire lock for wallet
  2. Build UTXO list
    • filter by unconfirmed and locked UTXOs
  3. Do coin selection
    • following armory-like method:
      • sort UTXOs in 10 different ways (including 1 random and some deterministically shifted)
      • based on scoring mechanism determine best coin selection for a 0 fee TX
      • if the best scoring selection is allowed free (high priority) then use it, otherwise ...
        • based on scoring mechenism determine best coin selection for a 10000 fee TX
        • if the best scoring selection is allowed for 10000 fee then use it, otherwise ...
          • based on scoring mechenism determine best coin selection
  4. Lock selected UTXO for a few seconds
  5. return UTXOs, required fee and change
    • UTXOs will also specify the address and matching path needed to sign it
Server (signing):
  1. add the 2nd signature to each input
  2. RPC sendrawtransaction
  3. TX is processed into the database like any ordinary TX (by indexer process) we just set a flag for the txhash that if it's requested through the API straight after it will sleep for a tiny bit for the processing to finish

Wallet Discovery

Wallet Discovery can be used for deleted wallets (unittests) and possibly imported wallets etc in the future.

Client:
  1. request wallet discovery
Server:
  1. For each possible blocktrail cosigning key repeat:
  2. Generate redeem script and address for first 200 derivations
  3. Check for transactions on addresses
  4. If transactions were found take the last derivation with transactions and do another +200 loops for that.

BlockTrail Cosign Key Upgrade

In case the blocktrail cosign key ever get's compromised we will generate a new cosign key and bump the blocktrail_key_index which acts as an identifier for the key. When wallets are then initialized we will send upgrade_key_index along to notify the client that it should stop using the old key and start using the new key. This will give a smooth transition to the new key and the client can then move all the BTC from his old addresses to new addresses to completely phase out the old key.

To make it easy to keep using the old key until it's phased out, the blocktrail_key_index is part of the BIP32 path, this way it's easy to identify which key should be used without the need of more metadata being transmitted.

Because of the Multi-Signature nature of the wallet a compromised blocktrail cosign key is not direct loss of bitcoin, an attacker will need to also get the passphrase for each wallet to get access to the bitcoins.

Future Plans

The above spec doesn't allow a lot of freedom/choice, in the future we'll offer more freedom/choice in the structure of the wallets to allow user to specify the amount of keys and the keys involved etc.

@nyknixny33
Copy link

I need help getting my coin off of the wallet. I have my passphrase and the recovery PDF but it won't work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment