Skip to main content

Bitcoin

Keystone is now integrated with multiple Bitcoin wallets, including Sparrow, Specter, BlueWallet, OKX, and UniSat.

Before continuing to read this page, please note that Keystone uses PSBT for Bitcoin transactions. If you are not familiar with PSBT, please take a moment to read the PSBT specification first.

Connect with Keystone

For Bitcoin, Keystone uses the UR type crypto-account to expose multiple extended public keys for P2PKH, P2SH, P2WPKH, and P2TR addresses. Software can utilize these keys to generate the desired addresses. Developers can use the SDK to retrieve and parse this data from the QR Code displayed on the Keystone device.

Here is a sample code snippet to scan the animated QR code and parse the data:

import KeystoneSDK, {UR, URType} from "@keystonehq/keystone-sdk"
import {AnimatedQRScanner} from "@keystonehq/animated-qr"

/**
* Represents a component that handles the scanning of an animated QR code to retrieve
* the crypto account information from a Keystone hardware wallet.
*
* The component uses the `AnimatedQRScanner` from `@keystonehq/animated-qr` to scan the QR code,
* and the `KeystoneSDK` to parse the scanned data into a human-readable account information format.
*/

const Account = () => {

/**
* Callback function to handle successful QR code scans.
*
* @param {Object} data - The data object containing the type and cbor encoded string.
* @param {string} data.type - The type of the scanned data.
* @param {string} data.cbor - The cbor encoded string representing the account information.
*/
const onSucceed = ({type, cbor}) => {
// Parses the crypto account from the scanned QR code data.
const account = KeystoneSDK.parseAccount(new UR(Buffer.from(cbor, "hex"), type))
console.log("crypto account: ", account);
}

/**
* Callback function to handle errors during QR code scanning.
*
* @param {string} errorMessage - The error message describing what went wrong during scanning.
*/
const onError = (errorMessage) => {
console.log("error: ", errorMessage);
}

// Renders the AnimatedQRScanner component with the specified handlers for success and error events.
return <AnimatedQRScanner handleScan={onSucceed} handleError={onError} urTypes={[URType.CryptoAccount]} />
}

Here is an example of the resulting data:

{
"masterFingerprint": "160927c4",
"keys": [{
"chain": "BTC",
"path": "m/84'/0'/0'", // BIP44 derivation path for P2WPKH
"publicKey": "02...",
"chainCode": "d9...",
"extendedPublicKey": "xpub..." // extended public key
}, {
"chain": "BTC",
"path": "m/49'/0'/0'", // P2SH
...
},
{
"chain": "BTC",
"path": "m/44'/0'/0'", // P2PKH
...
},
{
"chain": "BTC",
"path": "m/86'/0'/0'", // P2TR
...
},
]
}

Keystone will provide the master fingerprint and the extended public keys for each address type, allowing software wallets to select the necessary data to generate the desired addresses.

Genereate the unsigned PSBT

For Bitcoin transactions, Keystone follows the PSBT protocol to decode and sign transactions. Therefore, the software wallet must first generate the PSBT, encode it into the UR type crypto-psbt, and then embed it into QR codes. The Keystone SDK has been built to make this process easy to use.

Here is a sample code snippet demonstrating how to use the SDK for this purpose:

import KeystoneSDK from "@keystonehq/keystone-sdk";
import {AnimatedQRCode} from "@keystonehq/animated-qr";

const Bitcoin = () => {
const psbtHex = "70736274ff0100710200000001a6e52d0cf7bec16c454dc590966906f2f711d2ffb720bf141b41fd0cd3146a220000000000ffffffff02809698000000000016001473071357788c861241e6e991cc1f7933aa87444440ff100500000000160014d98f4c248e06e54d08bafdc213912aca80c0a34a000000000001011f00e1f505000000001600147ced797aa1e84df81e4b9dc8a46b8db7f4abae9122060341d94247fabfc265035f0a51bcfaca3b65709a7876698769a336b4142faa4bad18f23f9fd254000080000000800000008000000000000000000000220203ab7173024786ba14179c33db3b7bdf630039c24089409637323b560a4b1d025618f23f9fd2540000800000008000000080010000000000000000";
const keystoneSDK = new KeystoneSDK();
const ur = keystoneSDK.btc.generatePSBT(Buffer.from(psbtHex, "hex"));

return <AnimatedQRCode type={ur.type} cbor={ur.cbor.toString("hex")}/>
}
options={{
size: number, // optional, QR code width and length in UI, default 180px
capacity: number, // optional, the capacity of a single QR code, default 400
interval: number // optional, the QR code change time interval in mill seconds for animated QR code, default 100ms
}}

Here is a javascript sample code snippet demonstrating how to use the Keystone SDK to encode a PSBT into the UR type crypto-psbt and embed it into QR codes.

AnimatedQRCode will decide whether the animated QR codes are needed, the option props of AnimatedQRCode component can be used to control the size, capacity and the update interval of QR code. Please avoid setting the capacity too high, as larger value can make it more difficult for Keystone to scan.

Generate PSBT Example

If you are not familar with PSBT, you can find detailed information here. Please note that for Keystone signing, the bip32Derivation have to been included for each input and change output in the psbt. Here is an JS example to generate a PSBT for the native SegWit transaction.

import {networks, payments, Psbt} from "bitcoinjs-lib";

const psbt = new Psbt();

psbt.addInput({
hash: "226a14d30cfd411b14bf20b7ffd211f7f206699690c54d456cc1bef70c2de5a6", // The utxo hash
index: 0, // The utxo index
witnessUtxo: { // An example of a P2WPKH utxo
value: 100000000, // The utxo amount
script: payments.p2wpkh({
pubkey: Buffer.from("0341d94247fabfc265035f0a51bcfaca3b65709a7876698769a336b4142faa4bad", "hex"), // The public key this utxo locked
network: networks.bitcoin,
}).output,
},
bip32Derivation: [
{
masterFingerprint: Buffer.from("F23F9FD2", "hex"), // The master fingerprint
pubkey: Buffer.from("0341d94247fabfc265035f0a51bcfaca3b65709a7876698769a336b4142faa4bad", 'hex'), // The public key this utxo locked
path: "m/84'/0'/0'/0/0" // The public key HD path
}
] // Required by keystone to find the specific signing key**
})

psbt.addOutputs([
{
address: "bc1qwvr3x4mc3jrpys0xaxguc8mexw4gw3zyt3h05c", // The transfer target
value: 10000000 // The transfer value
},
{
address: "bc1qmx85cfywqmj56z96lhpp8yf2e2qvpg620f2pa6", // The change address
value: 85000000, // The change value
bip32Derivation: [
{
masterFingerprint: Buffer.from("F23F9FD2", "hex"),
pubkey: Buffer.from("03ab7173024786ba14179c33db3b7bdf630039c24089409637323b560a4b1d0256", 'hex'), // The change public key
path: "m/84'/0'/0'/1/0" // The change HD path
}
] // bip32Derivation are also required to make keystone to verify the transaction.
}
])

psbt.toHex()
// 70736274ff0100710200000001a6e52d0cf7bec16c454dc590966906f2f711d2ffb720bf141b41fd0cd3146a220000000000ffffffff02809698000000000016001473071357788c861241e6e991cc1f7933aa87444440ff100500000000160014d98f4c248e06e54d08bafdc213912aca80c0a34a000000000001011f00e1f505000000001600147ced797aa1e84df81e4b9dc8a46b8db7f4abae9122060341d94247fabfc265035f0a51bcfaca3b65709a7876698769a336b4142faa4bad18f23f9fd254000080000000800000008000000000000000000000220203ab7173024786ba14179c33db3b7bdf630039c24089409637323b560a4b1d025618f23f9fd2540000800000008000000080010000000000000000

Extract signed psbt

After Keystone scans the unsigned PSBT, it will verify and display the transaction details for user confirmation. Once the transaction is signed, Keystone will encode the signed psbt into the QR codes. The software wallet can then scan the QR codes to extract the psbt. Please note the signed psbt is not finalized, software wallet should take care of the finalization process.

Here are some code samples demonstrating how to use the SDK to achieve this.

import KeystoneSDK, {UR, URType} from "@keystonehq/keystone-sdk"
import {AnimatedQRScanner} from "@keystonehq/animated-qr"

const Bitcoin = () => {
const onSucceed = ({type, cbor}) => {
const psbtHex = keystoneSDK.btc.parsePSBT(new UR(Buffer.from(cbor, "hex"), type))
console.log("psbt hex: ", psbtHex);
}
const onError = (errorMessage) => {
console.log("error: ", errorMessage);
}

return <AnimatedQRScanner handleScan={onSucceed} handleError={onError} urTypes={[URType.CryptoPSBT]} />
}

AnimatedQRScanner helps scan the QR code on Keystone hardware wallet and returns signed psbt which can be parsed by KeystoneSDK.

Extract Tx From PSBT Example

An example of converting PSBT to network serialized transaction.

import {Psbt} from "bitcoinjs-lib";

const psbt = Psbt.fromHex(psbtHex)
let extractedTransaction
try {
extractedTransaction = psbt.finalizeAllInputs().extractTransaction()
} catch (_){
extractedTransaction = psbt.extractTransaction()
}
// network serialized transaction
console.log(extractedTransaction.toHex())

Sign message

Keystone currently supports BIP-322 message signing, utilizing PSBT for the signing process. For more details about BIP-322, you can check the documentation. Additionally, Keystone is compatible with QR message signing on SeedSigner.