LedgerHQ / app-bitcoin-new

Modern Bitcoin Application based on PSBT and Descriptors
Apache License 2.0
94 stars 72 forks source link

Ledger device: Invalid data received (0x6a80) #198

Closed generalAO closed 1 year ago

generalAO commented 1 year ago

Hi, im trying to sign a psbt transaction with Ledger, but got an error: message: 'Ledger device: Invalid data received (0x6a80)', statusCode: 27264, statusText: 'INCORRECT_DATA' I have 3 signers. 1 of them with Ledger device, 2 others just with external xpubs. The signing strategy is 2 of 3. I can sign and broadcast psbt with external keys, but when im trying to sign with Ledger, i got an error. What im doing wrong?

`import * as bitcoin from 'bitcoinjs-lib'; import { alice, bob, ledger } from './keys'; import { TRANSPORT_TIME_OUT, network } from './constants'; import { derivedPrivateKey, pubkeyFromXpub } from './utils'; import { ECPairFactory } from 'ecpair'; import ecc from '@bitcoinerlab/secp256k1'; import { createLedgerWalletPolicy } from './wallet'; import AppClient, { WalletPolicy } from 'ledger-bitcoin'; import Transport from '@ledgerhq/hw-transport-node-hid';

export async function prepareAndSign2of3Ledger() { const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys: [ Buffer.from(pubkeyFromXpub(ledger.xpub), 'hex'), Buffer.from(pubkeyFromXpub(alice.xpub), 'hex'), Buffer.from(pubkeyFromXpub(bob.xpub), 'hex'), ], network, });

const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network, });

const inputData = { hash: '***', index: 0, witnessScript: p2wsh.redeem?.output, witnessUtxo: { script: Buffer.from( '0020' + bitcoin.crypto.sha256(p2ms.output!).toString('hex'), 'hex', ), value: 1000, // value in satoshi }, };

const keyPairAlice = ECPair.fromPrivateKey( Buffer.from(derivedPrivateKey(alice.mnemonic) as string, 'hex'), { network: bitcoin.networks.testnet, }, ); const keyPairBob = ECPair.fromPrivateKey( Buffer.from(derivedPrivateKey(bob.mnemonic) as string, 'hex'), { network: bitcoin.networks.testnet, }, );

const psbt = new bitcoin.Psbt({ network }).addInput(inputData).addOutput({ address: '****', value: 850, });

// LEDGER. const walletPolicy = await createLedgerWalletPolicy([ ledger.xpub, [${alice.pubKeyFingerprintMaster}/49'/1'/0']${alice.xpub}, [${bob.pubKeyFingerprintMaster}/49'/1'/0']${bob.xpub}, ]);

const transport = await Transport.create( TRANSPORT_TIME_OUT, TRANSPORT_TIME_OUT, ); const app = new AppClient(transport);

const address = await app.getWalletAddress( walletPolicy, Buffer.from(ledger.policyHmac, 'hex'), 0, 0, false, );

const [index, partialSignature] = await app.signPsbt( psbt, walletPolicy, Buffer.from(ledger.policyHmac, 'hex'), );`

`export async function createLedgerWalletPolicy( pubkeys: string[], ): Promise { const descriptionTemplate = 'wsh(multi(2,@0/,@1/,@2/**))'; const transport = await Transport.create( TRANSPORT_TIME_OUT, TRANSPORT_TIME_OUT, ); const app = new AppClient(transport); const fpr = await app.getMasterFingerprint();

let ourPubkey; try { ourPubkey = await app.getExtendedPubkey(path, false); } catch (e) { await transport.close(); throw e; }

if (!pubkeys.includes(ourPubkey)) { await transport.close(); throw new Error('pubkey not good'); }

const ourKeyInfo = [${fpr}/49'/1'/0']${ourPubkey}; const tempPubKeys: string[] = []; pubkeys.forEach((key) => { if (ourPubkey === key) { tempPubKeys.push(ourKeyInfo); } else { if (key.startsWith('tpub')) { tempPubKeys.push(key); } else { tempPubKeys.push(key); } } });

const multisigPolicy = new WalletPolicy( '2 of 3 xpubs', descriptionTemplate, tempPubKeys, );

transport.close(); return multisigPolicy; }`

I tried also use psbtV2 const psbtV2 = new PsbtV2(); psbtV2.deserialize(Buffer.from(psbt.toBase64(), 'base64'));

bigspider commented 1 year ago

No need to use psbtv2, the library can convert from PsbtV0 on the fly.

How do you get the hmac? Was the wallet policy correctly registered beforehand? For testing, I suggest registering and using the wallet policy in the same script. Note that any tiny change in the wallet policy will invalidate the hmac.

generalAO commented 1 year ago

So i can use psbt created with bitcoinjs-lib. Right? By default in bitcoinjs-lib version of psbt is 2.

I receive walet policy after i registered it with Ledger. const [policyId, registeredPolicyHmac] = await app.registerWallet( multisigPolicy, ); Of course wallet policy is the same, but i will try to do it in the same script, just to exclude any differences.

generalAO commented 1 year ago

I added registering policy to the same script, but still get the same error

bigspider commented 1 year ago

If you can share the psbt encoded in base64, I'll check if anything is missing.

(I don't think bitcoinjs implemented psbtV2, but psbtV0 is fine)

generalAO commented 1 year ago

Psbt in Base64. Pls check it, because i can sign it with 2 other (non ledger) keys and successfully broadcast.

cHNidP8BAFICAAAAAXT4O7x9eMNn7ardXHS6FtjY9AXeT7r+71XKP30rg4LvAAAAAAD/////AVIDAAAAAAAAFgAUcrEfm4kwMqMWFRtFfVFhTVvkwhIAAAAAAAEBK+gDAAAAAAAAIgAgvetQm+nFAYsuJvBXI5UYMNPsxjhiSsLPR3u3tsgZUhgBBWlSIQMgbvWSyS19q/KwaqFO49pULo/W6fEbDje10tHXnnggkCEDkrbyTiC5A6k16l0b3zJLHyQfxI5HZ25ONnDRETNE25ohAhcOnDH1MkapKJpJVQMBYCKF3ANvoUh+YCC6hpzmQ3dEU64AAA==

bigspider commented 1 year ago

The PSBT is incomplete, it doesn't contain basically anything other than the unsigned transaction.

It is required to have all the information about the keys present in the transaction; in particular, the PSBT_GLOBAL_XPUB and the PSBT_IN_BIP32_DERIVATION are compulsory.

You can find a few complete psbts in this folder, might help as a reference on how they should look like: https://github.com/LedgerHQ/app-bitcoin-new/tree/develop/tests/psbt.

generalAO commented 1 year ago

Thx, i will check your psvt. But why i can sign transaction without a ledger? With keys generated by bip39/32 libraries? I thought if i can sign with external keys, then i can sign with Ledger too.

bigspider commented 1 year ago

Because hardware wallets perform a number of checks that you can skip if you are blind-signing, but that reduces security. Following the BIP174 standard guarantees that all the fields that signers might need are correctly filled.