2023-02-10 04:13:47 +01:00
// import assert from 'assert';
2023-03-15 21:42:25 +01:00
import BIP47Factory from '@spsina/bip47' ;
import assert from 'assert' ;
2024-04-09 18:14:14 +02:00
import * as bitcoin from 'bitcoinjs-lib' ;
2024-05-20 11:54:13 +02:00
import { ECPairFactory } from 'ecpair' ;
import * as BlueElectrum from '../../blue_modules/BlueElectrum' ;
2024-04-23 12:25:20 +02:00
import ecc from '../../blue_modules/noble_ecc' ;
2024-05-20 11:54:13 +02:00
import { HDLegacyP2PKHWallet , HDSegwitBech32Wallet } from '../../class' ;
2024-04-23 12:25:20 +02:00
2023-03-15 21:42:25 +01:00
const ECPair = ECPairFactory ( ecc ) ;
2023-02-10 04:13:47 +01:00
2023-12-24 23:27:50 +01:00
jest . setTimeout ( 90 * 1000 ) ;
2023-02-10 04:13:47 +01:00
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 ( ) = > {
2023-03-15 21:42:25 +01:00
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' ) ;
2023-02-10 04:13:47 +01:00
expect ( hd . getBIP47PaymentCode ( ) ) . toEqual (
'PM8TJS2JxQ5ztXUpBBRnpTbcUXbUHy2T1abfrb3KkAAtMEGNbey4oumH7Hc578WgQJhPjBxteQ5GHHToTYHE3A1w6p7tU6KSoFmWBVbFGjKPisZDbP97' ,
) ;
2023-03-15 21:42:25 +01:00
expect ( hd . allowBIP47 ( ) ) . toEqual ( true ) ;
2023-02-10 04:13:47 +01:00
await hd . fetchBIP47SenderPaymentCodes ( ) ;
2023-03-15 21:42:25 +01:00
expect ( hd . getBIP47SenderPaymentCodes ( ) . length ) . toBeGreaterThanOrEqual ( 3 ) ;
expect ( hd . getBIP47SenderPaymentCodes ( ) ) . toContain (
2023-02-10 04:13:47 +01:00
'PM8TJTLJbPRGxSbc8EJi42Wrr6QbNSaSSVJ5Y3E4pbCYiTHUskHg13935Ubb7q8tx9GVbh2UuRnBc3WSyJHhUrw8KhprKnn9eDznYGieTzFcwQRya4GA' ,
) ;
2023-03-15 21:42:25 +01:00
expect ( hd . getBIP47SenderPaymentCodes ( ) ) . toContain (
2023-02-10 04:13:47 +01:00
'PM8TJgndZSWCBPG5zCsqdXmCKLi7sP13jXuRp6b5X7G9geA3vRXQKAoXDf4Eym2RJB3vvcBdpDQT4vbo5QX7UfeV2ddjM8s79ERUTFS2ScKggSrciUsU' ,
) ;
2023-03-15 21:42:25 +01:00
expect ( hd . getBIP47SenderPaymentCodes ( ) ) . toContain (
2023-02-10 04:13:47 +01:00
'PM8TJNiWKcyiA2MsWCfuAr9jvhA5qMEdEkjNypEnUbxMRa1D5ttQWdggQ7ib9VNFbRBSuw7i6RkqPSkCMR1XGPSikJHaCSfqWtsb1fn4WNAXjp5JVL5z' ,
) ;
2023-03-15 21:42:25 +01:00
await hd . fetchBalance ( ) ;
2023-02-10 04:13:47 +01:00
await hd . fetchTransactions ( ) ;
expect ( hd . getTransactions ( ) . length ) . toBeGreaterThanOrEqual ( 4 ) ;
} ) ;
2023-03-15 21:42:25 +01:00
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 ,
) ;
2024-04-23 12:25:20 +02:00
assert . strictEqual (
w . getTransactions ( ) . find ( tx = > tx . txid === '06b4c14587182fd0474f265a77b156519b4778769a99c21623863a8194d0fa4f' ) ? . outputs ? . [ 1 ]
. scriptPubKey . addresses [ 0 ] ,
bobBip47 . getNotificationAddress ( ) ,
) ; // transaction is to Bob's notification address
2023-03-15 21:42:25 +01:00
} ) ;
2024-04-30 00:43:04 +02:00
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 === 1 ) ;
2024-05-15 23:46:54 +02:00
assert . ok ( walletSender . getBIP47NotificationTransaction ( bip47instanceReceiver . getSerializedPaymentCode ( ) ) ) ; // already notified in the past
assert . ok (
! walletSender . getBIP47NotificationTransaction (
2024-04-30 00:43:04 +02:00
'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 (
2024-05-15 23:46:54 +02:00
w . getBIP47NotificationTransaction (
2024-04-30 00:43:04 +02:00
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo' ,
) ,
) ;
assert . strictEqual (
2024-05-05 21:27:43 +02:00
w . getBip47CounterpartyByTxid ( '64058a49bb75481fc0bebbb0d84a4aceebe319f9d32929e73cefb21d83342e9f' ) ,
2024-04-30 00:43:04 +02:00
'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 (
2024-05-15 23:46:54 +02:00
w . getBIP47NotificationTransaction (
2024-04-30 00:43:04 +02:00
'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 (
2024-05-05 21:27:43 +02:00
w . getBip47CounterpartyByTxid ( '73a2ac70858c5b306b101a861d582f40c456a692096a4e4805aa739258c4400d' ) ,
2024-04-30 00:43:04 +02:00
'PM8TJi1RuCrgSHTzGMoayUf8xUW6zYBGXBPSWwTiMhMMwqto7G6NA4z9pN5Kn8Pbhryo2eaHMFRRcidCGdB3VCDXJD4DdPD2ZyG3ScLMEvtStAetvPMo' ,
) ; // we paid sparrow
2024-05-05 21:27:43 +02:00
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' ,
) ;
2024-04-30 00:43:04 +02:00
} ) ;
2024-05-15 23:46:54 +02:00
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 . deepStrictEqual ( w . getBIP47SenderPaymentCodes ( ) , [
'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
} ) ;
2023-02-10 04:13:47 +01:00
} ) ;