mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 09:50:15 +01:00
309 lines
13 KiB
TypeScript
309 lines
13 KiB
TypeScript
// import assert from 'assert';
|
|
import BIP47Factory from '@spsina/bip47';
|
|
import assert from 'assert';
|
|
import * as bitcoin from 'bitcoinjs-lib';
|
|
import { ECPairFactory } from 'ecpair';
|
|
|
|
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
|
|
import ecc from '../../blue_modules/noble_ecc';
|
|
import { HDLegacyP2PKHWallet, HDSegwitBech32Wallet } from '../../class';
|
|
|
|
const ECPair = ECPairFactory(ecc);
|
|
|
|
jest.setTimeout(90 * 1000);
|
|
|
|
afterAll(async () => {
|
|
// after all tests we close socket so the test suite can actually terminate
|
|
BlueElectrum.forceDisconnect();
|
|
});
|
|
|
|
beforeAll(async () => {
|
|
// awaiting for Electrum to be connected. For RN Electrum would naturally connect
|
|
// while app starts up, but for tests we need to wait for it
|
|
await BlueElectrum.connectMain();
|
|
});
|
|
|
|
describe('Bech32 Segwit HD (BIP84) with BIP47', () => {
|
|
it('should work', async () => {
|
|
const hd = new HDLegacyP2PKHWallet();
|
|
// @see https://gist.github.com/SamouraiDev/6aad669604c5930864bd
|
|
hd.setSecret('reward upper indicate eight swift arch injury crystal super wrestle already dentist');
|
|
|
|
expect(hd.getBIP47PaymentCode()).toEqual(
|
|
'PM8TJS2JxQ5ztXUpBBRnpTbcUXbUHy2T1abfrb3KkAAtMEGNbey4oumH7Hc578WgQJhPjBxteQ5GHHToTYHE3A1w6p7tU6KSoFmWBVbFGjKPisZDbP97',
|
|
);
|
|
|
|
expect(hd.allowBIP47()).toEqual(true);
|
|
|
|
await hd.fetchBIP47SenderPaymentCodes();
|
|
expect(hd.getBIP47SenderPaymentCodes().length).toBeGreaterThanOrEqual(3);
|
|
expect(hd.getBIP47SenderPaymentCodes()).toContain(
|
|
'PM8TJTLJbPRGxSbc8EJi42Wrr6QbNSaSSVJ5Y3E4pbCYiTHUskHg13935Ubb7q8tx9GVbh2UuRnBc3WSyJHhUrw8KhprKnn9eDznYGieTzFcwQRya4GA',
|
|
);
|
|
expect(hd.getBIP47SenderPaymentCodes()).toContain(
|
|
'PM8TJgndZSWCBPG5zCsqdXmCKLi7sP13jXuRp6b5X7G9geA3vRXQKAoXDf4Eym2RJB3vvcBdpDQT4vbo5QX7UfeV2ddjM8s79ERUTFS2ScKggSrciUsU',
|
|
);
|
|
expect(hd.getBIP47SenderPaymentCodes()).toContain(
|
|
'PM8TJNiWKcyiA2MsWCfuAr9jvhA5qMEdEkjNypEnUbxMRa1D5ttQWdggQ7ib9VNFbRBSuw7i6RkqPSkCMR1XGPSikJHaCSfqWtsb1fn4WNAXjp5JVL5z',
|
|
);
|
|
|
|
await hd.fetchBalance();
|
|
await hd.fetchTransactions();
|
|
expect(hd.getTransactions().length).toBeGreaterThanOrEqual(4);
|
|
});
|
|
|
|
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 bip47 = BIP47Factory(ecc).fromBip39Seed(w.getSecret(), undefined, w.getPassphrase());
|
|
const ourNotificationAddress = bip47.getNotificationAddress();
|
|
|
|
const publicBip47 = BIP47Factory(ecc).fromPaymentCode(w.getBIP47PaymentCode());
|
|
expect(ourNotificationAddress).toEqual(publicBip47.getNotificationAddress());
|
|
|
|
expect(ourNotificationAddress).toEqual('1EiP2kSqxNqRhn8MPMkrtSEqaWiCWLYyTS'); // our notif address
|
|
|
|
await w.fetchBIP47SenderPaymentCodes();
|
|
assert.ok(
|
|
w
|
|
.getBIP47SenderPaymentCodes()
|
|
.includes('PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo'),
|
|
); // sparrow payment code
|
|
|
|
assert.ok(w.weOwnAddress('bc1q57nwf9vfq2qsl80q37wq5h0tjytsk95vgjq4fe')); // this is an address that was derived (and paid) from counterparty payment code
|
|
|
|
const keyPair2 = ECPair.fromWIF(w._getWIFbyAddress('bc1q57nwf9vfq2qsl80q37wq5h0tjytsk95vgjq4fe') || '');
|
|
const address = bitcoin.payments.p2wpkh({
|
|
pubkey: keyPair2.publicKey,
|
|
}).address;
|
|
assert.strictEqual(address, 'bc1q57nwf9vfq2qsl80q37wq5h0tjytsk95vgjq4fe');
|
|
|
|
await w.fetchTransactions();
|
|
|
|
assert.ok(w.getTransactions().length >= 3);
|
|
|
|
assert.strictEqual(
|
|
w.getTransactions().find(tx => tx.txid === '64058a49bb75481fc0bebbb0d84a4aceebe319f9d32929e73cefb21d83342e9f')?.value,
|
|
100000,
|
|
); // initial deposit from sparrow after sparrow made a notification tx
|
|
|
|
assert.strictEqual(
|
|
w.getTransactions().find(tx => tx.txid === '06b4c14587182fd0474f265a77b156519b4778769a99c21623863a8194d0fa4f')?.value,
|
|
-22692,
|
|
); // notification tx to sparrow so we can pay sparrow
|
|
|
|
assert.strictEqual(
|
|
w.getTransactions().find(tx => tx.txid === '73a2ac70858c5b306b101a861d582f40c456a692096a4e4805aa739258c4400d')?.value,
|
|
-77308,
|
|
); // paying to sparrow
|
|
|
|
// now, constructing OP_RETURN data to notify sparrow about us
|
|
|
|
const aliceBip47 = bip47;
|
|
const keyPair = ECPair.fromWIF(w._getWIFbyAddress('bc1q57nwf9vfq2qsl80q37wq5h0tjytsk95vgjq4fe') || '');
|
|
const bobBip47 = BIP47Factory(ecc).fromPaymentCode(
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
);
|
|
const blindedPaymentCode = aliceBip47.getBlindedPaymentCode(
|
|
bobBip47,
|
|
keyPair.privateKey as Buffer,
|
|
// txid is reversed, as well as output number ()
|
|
Buffer.from('64058a49bb75481fc0bebbb0d84a4aceebe319f9d32929e73cefb21d83342e9f', 'hex').reverse().toString('hex') + '01000000',
|
|
);
|
|
|
|
assert.strictEqual(
|
|
blindedPaymentCode,
|
|
'0100039da7642943ec5d16c9bce09b71f240fe246d891fa3b52a7d236fece98318e1ae972f3747672f7e79a23fc88c4dc91a8d014233e14a9e4417e132405b6a6c166d00000000000000000000000000',
|
|
);
|
|
|
|
// checking that this is exactly a data payload we have in an actual notification transaction we have sent:
|
|
assert.strictEqual(
|
|
w.getTransactions().find(tx => tx.txid === '06b4c14587182fd0474f265a77b156519b4778769a99c21623863a8194d0fa4f')?.outputs?.[0]
|
|
?.scriptPubKey.hex,
|
|
'6a4c50' + blindedPaymentCode,
|
|
);
|
|
|
|
assert.strictEqual(
|
|
w.getTransactions().find(tx => tx.txid === '06b4c14587182fd0474f265a77b156519b4778769a99c21623863a8194d0fa4f')?.outputs?.[1]
|
|
.scriptPubKey.addresses[0],
|
|
bobBip47.getNotificationAddress(),
|
|
); // transaction is to Bob's notification address
|
|
});
|
|
|
|
it('can tell whom to notify and whom dont', 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);
|
|
await walletSender.fetchBIP47SenderPaymentCodes();
|
|
await walletSender.fetchBalance();
|
|
await walletSender.fetchTransactions();
|
|
|
|
assert.ok(walletSender.getTransactions().length >= 3);
|
|
assert.ok(walletSender._receive_payment_codes.length >= 2);
|
|
|
|
assert.ok(walletSender.getBIP47NotificationTransaction(bip47instanceReceiver.getSerializedPaymentCode())); // already notified in the past
|
|
assert.ok(
|
|
!walletSender.getBIP47NotificationTransaction(
|
|
'PM8TJdfXvRasx4WNpxky25ZKxhvfEiGYW9mka92tfiqDRSL7LQdxnC8uAk9k3okXctZowVwY2PUndjCQR6DHyuVVwqmy2aodmZNHgfFZcJRNTuBAXJCp',
|
|
),
|
|
); // random PC from interwebz. never interacted with him, so need to notify
|
|
});
|
|
|
|
it('can tell with which counterparty PC transaction is', 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');
|
|
|
|
w.switchBIP47(true);
|
|
|
|
await w.fetchBIP47SenderPaymentCodes();
|
|
await w.fetchBalance();
|
|
await w.fetchTransactions();
|
|
|
|
assert.ok(
|
|
w
|
|
.getBIP47SenderPaymentCodes()
|
|
.includes('PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo'),
|
|
); // sparrow payment code
|
|
|
|
assert.strictEqual(
|
|
w.getTransactions().find(tx => tx.txid === '64058a49bb75481fc0bebbb0d84a4aceebe319f9d32929e73cefb21d83342e9f')?.value,
|
|
100000,
|
|
); // sparrow paid us after sparrow made a notification tx
|
|
|
|
assert.ok(
|
|
w.getBIP47NotificationTransaction(
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
),
|
|
);
|
|
|
|
assert.strictEqual(
|
|
w.getBip47CounterpartyByTxid('64058a49bb75481fc0bebbb0d84a4aceebe319f9d32929e73cefb21d83342e9f'),
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
); // we got paid
|
|
|
|
// pretending that user added this PC as a counterparty (sent a notif tx) to pay to:
|
|
w.addBIP47Receiver(
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
);
|
|
assert.ok(
|
|
w.getBIP47NotificationTransaction(
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
),
|
|
); // dont need to notify
|
|
|
|
// prior to sync, we have no info on which joint address shall be available
|
|
assert.strictEqual(
|
|
w._next_free_payment_code_address_index_send
|
|
.PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo,
|
|
undefined, // basically zero
|
|
);
|
|
|
|
await w.syncBip47ReceiversAddresses(
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
);
|
|
|
|
// after sync, we know that index 0 was used so index 1 is next free:
|
|
assert.strictEqual(
|
|
w._next_free_payment_code_address_index_send
|
|
.PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo,
|
|
1,
|
|
);
|
|
|
|
assert.strictEqual(
|
|
w.getBip47CounterpartyByTxid('73a2ac70858c5b306b101a861d582f40c456a692096a4e4805aa739258c4400d'),
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
); // we paid sparrow
|
|
|
|
let txWithCounterparty = w.getTransactions().find(tx => tx.txid === '73a2ac70858c5b306b101a861d582f40c456a692096a4e4805aa739258c4400d');
|
|
assert(txWithCounterparty);
|
|
assert.strictEqual(
|
|
txWithCounterparty.counterparty,
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
);
|
|
|
|
txWithCounterparty = w.getTransactions().find(tx => tx.txid === '64058a49bb75481fc0bebbb0d84a4aceebe319f9d32929e73cefb21d83342e9f');
|
|
assert(txWithCounterparty);
|
|
assert.strictEqual(
|
|
txWithCounterparty.counterparty,
|
|
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo',
|
|
);
|
|
});
|
|
|
|
it('can tell with which counterparty PC transaction is (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]);
|
|
w.switchBIP47(true);
|
|
|
|
await w.fetchBIP47SenderPaymentCodes();
|
|
await w.fetchBalance();
|
|
await w.fetchTransactions();
|
|
|
|
assert.strictEqual(
|
|
w.getTransactions().find(tx => tx.txid === '64058a49bb75481fc0bebbb0d84a4aceebe319f9d32929e73cefb21d83342e9f')?.value,
|
|
-102308,
|
|
); // we paid samurai
|
|
|
|
assert.ok(
|
|
w
|
|
.getBIP47SenderPaymentCodes()
|
|
.includes('PM8TJXuZNUtSibuXKFM6bhCxpNaSye6r4px2GXRV5v86uRdH9Raa8ZtXEkG7S4zLREf4ierjMsxLXSFTbRVUnRmvjw9qnc7zZbyXyBstSmjcb7uVcDYF'),
|
|
); // samurai can pay us
|
|
|
|
assert.deepStrictEqual(w.getBIP47ReceiverPaymentCodes(), []); // we can pay no-one
|
|
|
|
assert.ok(!w.getBip47CounterpartyByTxid('64058a49bb75481fc0bebbb0d84a4aceebe319f9d32929e73cefb21d83342e9f')); // dont know whom we paid
|
|
|
|
w.addBIP47Receiver(
|
|
'PM8TJXuZNUtSibuXKFM6bhCxpNaSye6r4px2GXRV5v86uRdH9Raa8ZtXEkG7S4zLREf4ierjMsxLXSFTbRVUnRmvjw9qnc7zZbyXyBstSmjcb7uVcDYF',
|
|
);
|
|
assert.deepStrictEqual(w.getBIP47ReceiverPaymentCodes(), [
|
|
'PM8TJXuZNUtSibuXKFM6bhCxpNaSye6r4px2GXRV5v86uRdH9Raa8ZtXEkG7S4zLREf4ierjMsxLXSFTbRVUnRmvjw9qnc7zZbyXyBstSmjcb7uVcDYF',
|
|
]); // we can now pay samurai
|
|
|
|
// await w.fetchBIP47SenderPaymentCodes();
|
|
// await w.fetchBalance();
|
|
// await w.fetchTransactions();
|
|
await w.syncBip47ReceiversAddresses(
|
|
'PM8TJXuZNUtSibuXKFM6bhCxpNaSye6r4px2GXRV5v86uRdH9Raa8ZtXEkG7S4zLREf4ierjMsxLXSFTbRVUnRmvjw9qnc7zZbyXyBstSmjcb7uVcDYF',
|
|
);
|
|
|
|
assert.strictEqual(
|
|
w.getBip47CounterpartyByTxid('64058a49bb75481fc0bebbb0d84a4aceebe319f9d32929e73cefb21d83342e9f'),
|
|
'PM8TJXuZNUtSibuXKFM6bhCxpNaSye6r4px2GXRV5v86uRdH9Raa8ZtXEkG7S4zLREf4ierjMsxLXSFTbRVUnRmvjw9qnc7zZbyXyBstSmjcb7uVcDYF',
|
|
); // we paid samurai, and now we can seethat
|
|
});
|
|
});
|