mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-22 15:04:50 +01:00
366 lines
14 KiB
TypeScript
366 lines
14 KiB
TypeScript
import BIP47Factory from '@spsina/bip47';
|
|
import assert from 'assert';
|
|
import * as bitcoin from 'bitcoinjs-lib';
|
|
import { ECPairFactory } from 'ecpair';
|
|
|
|
import ecc from '../../blue_modules/noble_ecc';
|
|
import { HDSegwitBech32Wallet, WatchOnlyWallet } from '../../class';
|
|
import { CreateTransactionUtxo } from '../../class/wallets/types';
|
|
|
|
const ECPair = ECPairFactory(ecc);
|
|
|
|
describe('Bech32 Segwit HD (BIP84) with BIP47', () => {
|
|
it('should work', async () => {
|
|
const bobWallet = new HDSegwitBech32Wallet();
|
|
// @see https://gist.github.com/SamouraiDev/6aad669604c5930864bd
|
|
bobWallet.setSecret('reward upper indicate eight swift arch injury crystal super wrestle already dentist');
|
|
|
|
expect(bobWallet.getBIP47PaymentCode()).toEqual(
|
|
'PM8TJS2JxQ5ztXUpBBRnpTbcUXbUHy2T1abfrb3KkAAtMEGNbey4oumH7Hc578WgQJhPjBxteQ5GHHToTYHE3A1w6p7tU6KSoFmWBVbFGjKPisZDbP97',
|
|
);
|
|
assert.strictEqual(bobWallet.getBIP47NotificationAddress(), '1ChvUUvht2hUQufHBXF8NgLhW8SwE2ecGV'); // our notif address
|
|
assert.ok(!bobWallet.weOwnAddress('1JDdmqFLhpzcUwPeinhJbUPw4Co3aWLyzW')); // alice notif address, we dont own it
|
|
});
|
|
|
|
it('getters, setters, flags work', async () => {
|
|
const w = new HDSegwitBech32Wallet();
|
|
await w.generate();
|
|
|
|
expect(w.allowBIP47()).toEqual(true);
|
|
|
|
expect(w.isBIP47Enabled()).toEqual(false);
|
|
w.switchBIP47(true);
|
|
expect(w.isBIP47Enabled()).toEqual(true);
|
|
w.switchBIP47(false);
|
|
expect(w.isBIP47Enabled()).toEqual(false);
|
|
|
|
// checking that derived watch-only does not support that:
|
|
const ww = new WatchOnlyWallet();
|
|
ww.setSecret(w.getXpub());
|
|
expect(ww.allowBIP47()).toEqual(false);
|
|
});
|
|
|
|
it('should work (samurai)', async () => {
|
|
if (!process.env.BIP47_HD_MNEMONIC) {
|
|
console.error('process.env.BIP47_HD_MNEMONIC not set, skipped');
|
|
return;
|
|
}
|
|
|
|
const w = new HDSegwitBech32Wallet();
|
|
w.setSecret(process.env.BIP47_HD_MNEMONIC.split(':')[0]);
|
|
w.setPassphrase('1');
|
|
|
|
expect(w.getBIP47PaymentCode()).toEqual(
|
|
'PM8TJXuZNUtSibuXKFM6bhCxpNaSye6r4px2GXRV5v86uRdH9Raa8ZtXEkG7S4zLREf4ierjMsxLXSFTbRVUnRmvjw9qnc7zZbyXyBstSmjcb7uVcDYF',
|
|
);
|
|
|
|
expect(w._getExternalAddressByIndex(0)).toEqual('bc1q07l355j4yd5kyut36vjxn2u60d3dknnpt39t6y');
|
|
|
|
const ourNotificationAddress = w.getBIP47NotificationAddress();
|
|
|
|
const publicBip47 = BIP47Factory(ecc).fromPaymentCode(w.getBIP47PaymentCode());
|
|
expect(ourNotificationAddress).toEqual(publicBip47.getNotificationAddress()); // same address we derived internally for ourselves and from public Payment Code
|
|
expect(ourNotificationAddress).toEqual('1EiP2kSqxNqRhn8MPMkrtSEqaWiCWLYyTS'); // our notif address
|
|
|
|
// since we dont do network calls in unit test we cant get counterparties payment codes from our notif address,
|
|
// and thus, dont know collaborative addresses with our payers. lets hardcode our counterparty payment code to test
|
|
// this functionality
|
|
|
|
assert.deepStrictEqual(w.getBIP47SenderPaymentCodes(), []);
|
|
|
|
w._receive_payment_codes = [
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
];
|
|
|
|
assert.deepStrictEqual(w.getBIP47SenderPaymentCodes(), [
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
]);
|
|
|
|
assert.ok(w.weOwnAddress('bc1q57nwf9vfq2qsl80q37wq5h0tjytsk95vgjq4fe'));
|
|
const pubkey = w._getPubkeyByAddress('bc1q57nwf9vfq2qsl80q37wq5h0tjytsk95vgjq4fe');
|
|
const path = w._getDerivationPathByAddress('bc1q57nwf9vfq2qsl80q37wq5h0tjytsk95vgjq4fe');
|
|
assert.ok(pubkey);
|
|
assert.ok(path);
|
|
|
|
const keyPair2 = ECPair.fromWIF(w._getWIFbyAddress('bc1q57nwf9vfq2qsl80q37wq5h0tjytsk95vgjq4fe') || '');
|
|
const address = bitcoin.payments.p2wpkh({
|
|
pubkey: keyPair2.publicKey,
|
|
}).address;
|
|
|
|
assert.strictEqual(address, 'bc1q57nwf9vfq2qsl80q37wq5h0tjytsk95vgjq4fe');
|
|
});
|
|
|
|
it('should work (sparrow)', async () => {
|
|
if (!process.env.BIP47_HD_MNEMONIC) {
|
|
console.error('process.env.BIP47_HD_MNEMONIC not set, skipped');
|
|
return;
|
|
}
|
|
|
|
const w = new HDSegwitBech32Wallet();
|
|
w.setSecret(process.env.BIP47_HD_MNEMONIC.split(':')[1]);
|
|
|
|
assert.strictEqual(
|
|
w.getXpub(),
|
|
'zpub6r4KaQRsLuhHSGx8b9wGHh18UnawBs49jtiDzZYh9DSgKGwD72jWR3v54fkyy1UKVxt9HvCkYHmMAUe2YjKefofWzYp9YD62sUp6nNsEDMs',
|
|
);
|
|
|
|
expect(w.getBIP47PaymentCode()).toEqual(
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
);
|
|
|
|
const ourNotificationAddress = w.getBIP47NotificationAddress();
|
|
|
|
const publicBip47 = BIP47Factory(ecc).fromPaymentCode(w.getBIP47PaymentCode());
|
|
expect(ourNotificationAddress).toEqual(publicBip47.getNotificationAddress()); // same address we derived internally for ourselves and from public Payment Code
|
|
|
|
expect(ourNotificationAddress).toEqual('16xPugarxLzuNdhDu6XCMJBsMYrTN2fghN'); // our notif address
|
|
});
|
|
|
|
it('should be able to create notification transaction', async () => {
|
|
if (!process.env.BIP47_HD_MNEMONIC) {
|
|
console.error('process.env.BIP47_HD_MNEMONIC not set, skipped');
|
|
return;
|
|
}
|
|
|
|
// whom we are going to notify:
|
|
const bip47instanceReceiver = BIP47Factory(ecc).fromBip39Seed(process.env.BIP47_HD_MNEMONIC.split(':')[0], undefined, '1');
|
|
|
|
// notifier:
|
|
const walletSender = new HDSegwitBech32Wallet();
|
|
walletSender.setSecret(process.env.BIP47_HD_MNEMONIC.split(':')[1]);
|
|
walletSender.switchBIP47(true);
|
|
|
|
// lets produce a notification transaction and verify that receiver can actually use it
|
|
|
|
// since we cant do network calls, we hardcode our senders so later `_getWIFbyAddress`
|
|
// could resolve wif for address deposited by him (funds we want to use reside on addresses from BIP47)
|
|
walletSender._receive_payment_codes = [
|
|
'PM8TJXuZNUtSibuXKFM6bhCxpNaSye6r4px2GXRV5v86uRdH9Raa8ZtXEkG7S4zLREf4ierjMsxLXSFTbRVUnRmvjw9qnc7zZbyXyBstSmjcb7uVcDYF',
|
|
];
|
|
|
|
const utxos: CreateTransactionUtxo[] = [
|
|
{
|
|
value: 74822,
|
|
address: 'bc1qaxxc4gwx6rd6rymq08qwpxhesd4jqu93lvjsyt',
|
|
txid: '73a2ac70858c5b306b101a861d582f40c456a692096a4e4805aa739258c4400d',
|
|
vout: 0,
|
|
wif: walletSender._getWIFbyAddress('bc1qaxxc4gwx6rd6rymq08qwpxhesd4jqu93lvjsyt') + '',
|
|
},
|
|
{
|
|
value: 894626,
|
|
address: 'bc1qr60ek5gtjs04akcp9f5x25v5gyp2tmspx78jxl',
|
|
txid: '64058a49bb75481fc0bebbb0d84a4aceebe319f9d32929e73cefb21d83342e9f',
|
|
vout: 0,
|
|
wif: walletSender._getWIFbyAddress('bc1qr60ek5gtjs04akcp9f5x25v5gyp2tmspx78jxl') + '',
|
|
},
|
|
];
|
|
|
|
const changeAddress = 'bc1q7vraw79vcf7qhnefeaul578h7vjc7tr95ywfuq';
|
|
|
|
const { tx, fee } = walletSender.createBip47NotificationTransaction(
|
|
utxos,
|
|
bip47instanceReceiver.getSerializedPaymentCode(),
|
|
33,
|
|
changeAddress,
|
|
);
|
|
assert(tx);
|
|
|
|
const recoveredPaymentCode = bip47instanceReceiver.getPaymentCodeFromRawNotificationTransaction(tx.toHex());
|
|
assert.strictEqual(walletSender.getBIP47PaymentCode(), recoveredPaymentCode); // accepted!
|
|
|
|
assert.strictEqual(
|
|
tx.outs[1].script.toString('hex'),
|
|
'6a4c500100031c9282bd392ee9700a50d7161c5f76f7b89e7a6fb551bfd5660e79cc7c8d8e7f7676b25ab4db90a96fadfa1254741e09b35e27c7dc1abcd2dc93c4c32732f45400000000000000000000000000',
|
|
);
|
|
|
|
const actualFeerate = fee / tx.virtualSize();
|
|
assert.strictEqual(Math.round(actualFeerate), 33);
|
|
});
|
|
|
|
it('should be able to pay to PC', async () => {
|
|
if (!process.env.BIP47_HD_MNEMONIC) {
|
|
console.error('process.env.BIP47_HD_MNEMONIC not set, skipped');
|
|
return;
|
|
}
|
|
|
|
// whom we are going to pay:
|
|
const bip47instanceReceiver = BIP47Factory(ecc).fromBip39Seed(process.env.BIP47_HD_MNEMONIC.split(':')[0], undefined, '1');
|
|
|
|
// notifier:
|
|
const walletSender = new HDSegwitBech32Wallet();
|
|
walletSender.setSecret(process.env.BIP47_HD_MNEMONIC.split(':')[1]);
|
|
walletSender.switchBIP47(true);
|
|
|
|
// since we cant do network calls, we hardcode our senders so later `_getWIFbyAddress`
|
|
// could resolve wif for address deposited by him (funds we want to use reside on addresses from BIP47)
|
|
walletSender._receive_payment_codes = [
|
|
'PM8TJXuZNUtSibuXKFM6bhCxpNaSye6r4px2GXRV5v86uRdH9Raa8ZtXEkG7S4zLREf4ierjMsxLXSFTbRVUnRmvjw9qnc7zZbyXyBstSmjcb7uVcDYF',
|
|
];
|
|
|
|
walletSender.addBIP47Receiver(bip47instanceReceiver.getSerializedPaymentCode());
|
|
|
|
const utxos: CreateTransactionUtxo[] = [
|
|
{
|
|
value: 74822,
|
|
address: 'bc1qaxxc4gwx6rd6rymq08qwpxhesd4jqu93lvjsyt',
|
|
txid: '73a2ac70858c5b306b101a861d582f40c456a692096a4e4805aa739258c4400d',
|
|
vout: 0,
|
|
wif: walletSender._getWIFbyAddress('bc1qaxxc4gwx6rd6rymq08qwpxhesd4jqu93lvjsyt') + '',
|
|
},
|
|
{
|
|
value: 894626,
|
|
address: 'bc1qr60ek5gtjs04akcp9f5x25v5gyp2tmspx78jxl',
|
|
txid: '64058a49bb75481fc0bebbb0d84a4aceebe319f9d32929e73cefb21d83342e9f',
|
|
vout: 0,
|
|
wif: walletSender._getWIFbyAddress('bc1qr60ek5gtjs04akcp9f5x25v5gyp2tmspx78jxl') + '',
|
|
},
|
|
];
|
|
|
|
const changeAddress = 'bc1q7vraw79vcf7qhnefeaul578h7vjc7tr95ywfuq';
|
|
|
|
const { tx, fee } = walletSender.createTransaction(
|
|
utxos,
|
|
[
|
|
{ address: bip47instanceReceiver.getSerializedPaymentCode(), value: 10234 },
|
|
{ address: '13HaCAB4jf7FYSZexJxoczyDDnutzZigjS', value: 22000 },
|
|
],
|
|
6,
|
|
changeAddress,
|
|
);
|
|
assert(tx);
|
|
|
|
assert.strictEqual(tx.outs[0].value, 10234);
|
|
assert.strictEqual(
|
|
bitcoin.address.fromOutputScript(tx.outs[0].script),
|
|
walletSender._getBIP47AddressSend(bip47instanceReceiver.getSerializedPaymentCode(), 0),
|
|
);
|
|
|
|
assert.strictEqual(tx.outs[1].value, 22000);
|
|
assert.strictEqual(bitcoin.address.fromOutputScript(tx.outs[1].script), '13HaCAB4jf7FYSZexJxoczyDDnutzZigjS');
|
|
|
|
const actualFeerate = fee / tx.virtualSize();
|
|
assert.strictEqual(Math.round(actualFeerate), 6);
|
|
|
|
// lets retry, but pretend that a few sender's addresses were used:
|
|
|
|
walletSender._next_free_payment_code_address_index_send[bip47instanceReceiver.getSerializedPaymentCode()] = 6;
|
|
|
|
const { tx: tx2 } = walletSender.createTransaction(
|
|
utxos,
|
|
[
|
|
{ address: bip47instanceReceiver.getSerializedPaymentCode(), value: 10234 },
|
|
{ address: '13HaCAB4jf7FYSZexJxoczyDDnutzZigjS', value: 22000 },
|
|
],
|
|
6,
|
|
changeAddress,
|
|
);
|
|
assert(tx2);
|
|
|
|
assert.strictEqual(
|
|
bitcoin.address.fromOutputScript(tx2.outs[0].script),
|
|
walletSender._getBIP47AddressSend(bip47instanceReceiver.getSerializedPaymentCode(), 6),
|
|
);
|
|
});
|
|
|
|
it('should be able to pay to PC (BIP-352 SilentPayments)', async () => {
|
|
if (!process.env.BIP47_HD_MNEMONIC) {
|
|
console.error('process.env.BIP47_HD_MNEMONIC not set, skipped');
|
|
return;
|
|
}
|
|
|
|
const walletSender = new HDSegwitBech32Wallet();
|
|
walletSender.setSecret(process.env.BIP47_HD_MNEMONIC.split(':')[1]);
|
|
walletSender.switchBIP47(true);
|
|
|
|
const utxos: CreateTransactionUtxo[] = [
|
|
{
|
|
txid: 'ff2b3dc0f16ad96e48f59232421113330781a88ca9b4518846ad9a626260abd3',
|
|
vout: 1,
|
|
address: 'bc1qr7trw22djl93c2vz43ftlmaexhvph8w0v4f6ap',
|
|
value: 195928,
|
|
wif: walletSender._getWIFbyAddress('bc1qr7trw22djl93c2vz43ftlmaexhvph8w0v4f6ap') as string,
|
|
},
|
|
];
|
|
const changeAddress = 'bc1q7vraw79vcf7qhnefeaul578h7vjc7tr95ywfuq';
|
|
|
|
const { tx, fee } = walletSender.createTransaction(
|
|
utxos,
|
|
[
|
|
{ address: '13HaCAB4jf7FYSZexJxoczyDDnutzZigjS', value: 22000 },
|
|
{
|
|
address: 'sp1qqvvnsd3xnjpmx8hnn2ua0e9sllm34t9jydf8qfesgc7nhdxgzksjwqlrxx37nfzsg6rure5vwa92fksd6f5a6rk05kr07twhd55u3ahquy2v7t6s',
|
|
value: 10234,
|
|
},
|
|
],
|
|
6,
|
|
changeAddress,
|
|
);
|
|
assert(tx);
|
|
|
|
const legacyAddressDestination = tx.outs.find(o => bitcoin.address.fromOutputScript(o.script) === '13HaCAB4jf7FYSZexJxoczyDDnutzZigjS');
|
|
assert.strictEqual(legacyAddressDestination?.value, 22000);
|
|
|
|
const spDestinatiob = tx.outs.find(o => o.value === 10234);
|
|
assert.strictEqual(
|
|
bitcoin.address.fromOutputScript(spDestinatiob!.script!),
|
|
'bc1pu7dwaehvur4lpc7cqmynnjgx5ngthk574p05mgwxf9lecv4r6j5s02nhxq',
|
|
);
|
|
|
|
const changeDestination = tx.outs.find(
|
|
o => bitcoin.address.fromOutputScript(o.script) === 'bc1q7vraw79vcf7qhnefeaul578h7vjc7tr95ywfuq',
|
|
);
|
|
|
|
const calculatedFee = 195928 - changeDestination!.value - spDestinatiob!.value - legacyAddressDestination!.value;
|
|
|
|
assert.strictEqual(fee, calculatedFee);
|
|
|
|
const actualFeerate = fee / tx.virtualSize();
|
|
assert.strictEqual(Math.round(actualFeerate), 6);
|
|
});
|
|
|
|
it('can unwrap addresses to send & receive', () => {
|
|
if (!process.env.BIP47_HD_MNEMONIC) {
|
|
console.error('process.env.BIP47_HD_MNEMONIC not set, skipped');
|
|
return;
|
|
}
|
|
|
|
const w = new HDSegwitBech32Wallet();
|
|
w.setSecret(process.env.BIP47_HD_MNEMONIC.split(':')[0]);
|
|
w.setPassphrase('1');
|
|
|
|
const addr = w._getBIP47AddressReceive(
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
0,
|
|
);
|
|
assert.strictEqual(addr, 'bc1q57nwf9vfq2qsl80q37wq5h0tjytsk95vgjq4fe');
|
|
|
|
const addr2 = w._getBIP47AddressSend(
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
0,
|
|
);
|
|
|
|
assert.strictEqual(addr2, 'bc1qaxxc4gwx6rd6rymq08qwpxhesd4jqu93lvjsyt');
|
|
|
|
assert.strictEqual(w.getAllExternalAddresses().length, 20); // exactly gap limit for external addresses
|
|
assert.ok(!w.getAllExternalAddresses().includes(addr)); // joint address to _receive_ is not included
|
|
|
|
// since we dont do network calls in unit test we cant get counterparties payment codes from our notif address,
|
|
// and thus, dont know collaborative addresses with our payers. lets hardcode our counterparty payment code to test
|
|
// this functionality
|
|
|
|
assert.deepStrictEqual(w.getBIP47SenderPaymentCodes(), []);
|
|
|
|
w.switchBIP47(true);
|
|
|
|
w._receive_payment_codes = [
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
];
|
|
|
|
assert.deepStrictEqual(w.getBIP47SenderPaymentCodes(), [
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
]);
|
|
|
|
assert.ok(w.getAllExternalAddresses().includes(addr)); // joint address to _receive_ is included
|
|
assert.ok(w.getAllExternalAddresses().length > 20);
|
|
});
|
|
});
|