@bsv/sdk 1.1.25 → 1.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
- package/dist/cjs/src/transaction/Transaction.js +154 -34
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/transaction/MerklePath.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +154 -34
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
- package/dist/types/src/transaction/Transaction.d.ts +44 -4
- package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/docs/transaction.md +65 -5
- package/package.json +1 -1
- package/src/transaction/MerklePath.ts +11 -11
- package/src/transaction/Transaction.ts +195 -59
- package/src/transaction/__tests/Transaction.test.ts +222 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import BigNumber from '../../../dist/cjs/src/primitives/BigNumber'
|
|
2
2
|
import TransactionSignature from '../../../dist/cjs/src/primitives/TransactionSignature'
|
|
3
|
-
import { toHex, toArray } from '../../../dist/cjs/src/primitives/utils'
|
|
3
|
+
import { toHex, toArray, Writer } from '../../../dist/cjs/src/primitives/utils'
|
|
4
4
|
import Script from '../../../dist/cjs/src/script/Script'
|
|
5
5
|
import UnlockingScript from '../../../dist/cjs/src/script/UnlockingScript'
|
|
6
6
|
import LockingScript from '../../../dist/cjs/src/script/LockingScript'
|
|
@@ -10,6 +10,8 @@ import PrivateKey from '../../../dist/cjs/src/primitives/PrivateKey'
|
|
|
10
10
|
import Curve from '../../../dist/cjs/src/primitives/Curve'
|
|
11
11
|
import P2PKH from '../../../dist/cjs/src/script/templates/P2PKH'
|
|
12
12
|
import fromUtxo from '../../../dist/cjs/src/compat/Utxo'
|
|
13
|
+
import MerklePath from '../../../dist/cjs/src/transaction/MerklePath'
|
|
14
|
+
import { BEEF_MAGIC } from '../../../dist/cjs/src/transaction/Beef'
|
|
13
15
|
|
|
14
16
|
import sighashVectors from '../../primitives/__tests/sighash.vectors'
|
|
15
17
|
import invalidTransactions from './tx.invalid.vectors'
|
|
@@ -422,7 +424,7 @@ describe('Transaction', () => {
|
|
|
422
424
|
status: 200,
|
|
423
425
|
statusText: 'OK',
|
|
424
426
|
headers: {
|
|
425
|
-
get
|
|
427
|
+
get(key: string) {
|
|
426
428
|
if (key === 'Content-Type') {
|
|
427
429
|
return 'application/json'
|
|
428
430
|
}
|
|
@@ -501,7 +503,7 @@ describe('Transaction', () => {
|
|
|
501
503
|
status: 200,
|
|
502
504
|
statusText: 'OK',
|
|
503
505
|
headers: {
|
|
504
|
-
get
|
|
506
|
+
get(key: string) {
|
|
505
507
|
if (key === 'Content-Type') {
|
|
506
508
|
return 'application/json'
|
|
507
509
|
}
|
|
@@ -616,4 +618,221 @@ describe('Transaction', () => {
|
|
|
616
618
|
})
|
|
617
619
|
})
|
|
618
620
|
})
|
|
621
|
+
|
|
622
|
+
describe('Atomic BEEF', () => {
|
|
623
|
+
it('should serialize a transaction to Atomic BEEF format correctly', async () => {
|
|
624
|
+
const privateKey = new PrivateKey(1)
|
|
625
|
+
const publicKey = new Curve().g.mul(privateKey)
|
|
626
|
+
const publicKeyHash = hash160(publicKey.encode(true)) as number[]
|
|
627
|
+
const p2pkh = new P2PKH()
|
|
628
|
+
|
|
629
|
+
// Create a simple transaction
|
|
630
|
+
const sourceTx = new Transaction(1, [], [{
|
|
631
|
+
lockingScript: p2pkh.lock(publicKeyHash),
|
|
632
|
+
satoshis: 10000
|
|
633
|
+
}], 0)
|
|
634
|
+
|
|
635
|
+
const spendTx = new Transaction(1, [{
|
|
636
|
+
sourceTransaction: sourceTx,
|
|
637
|
+
sourceOutputIndex: 0,
|
|
638
|
+
unlockingScriptTemplate: p2pkh.unlock(privateKey),
|
|
639
|
+
sequence: 0xffffffff
|
|
640
|
+
}], [{
|
|
641
|
+
satoshis: 9000,
|
|
642
|
+
lockingScript: p2pkh.lock(publicKeyHash)
|
|
643
|
+
}], 0)
|
|
644
|
+
|
|
645
|
+
// Sign the transaction
|
|
646
|
+
await spendTx.fee()
|
|
647
|
+
await spendTx.sign()
|
|
648
|
+
|
|
649
|
+
// Assign a MerklePath to the source transaction to simulate mined transaction
|
|
650
|
+
const sourceTxID = sourceTx.id('hex')
|
|
651
|
+
const merklePath = new MerklePath(1000, [
|
|
652
|
+
[
|
|
653
|
+
{ offset: 0, hash: sourceTxID, txid: true },
|
|
654
|
+
{ offset: 1, duplicate: true }
|
|
655
|
+
]
|
|
656
|
+
])
|
|
657
|
+
sourceTx.merklePath = merklePath
|
|
658
|
+
|
|
659
|
+
// Serialize to Atomic BEEF
|
|
660
|
+
const atomicBEEF = spendTx.toAtomicBEEF()
|
|
661
|
+
expect(atomicBEEF).toBeDefined()
|
|
662
|
+
// Verify that the Atomic BEEF starts with the correct prefix and TXID
|
|
663
|
+
const expectedPrefix = [0x01, 0x01, 0x01, 0x01]
|
|
664
|
+
expect(atomicBEEF.slice(0, 4)).toEqual(expectedPrefix)
|
|
665
|
+
const txid = spendTx.id()
|
|
666
|
+
expect(atomicBEEF.slice(4, 36)).toEqual(txid)
|
|
667
|
+
|
|
668
|
+
// Deserialize from Atomic BEEF
|
|
669
|
+
const deserializedTx = Transaction.fromAtomicBEEF(atomicBEEF)
|
|
670
|
+
expect(deserializedTx.toHex()).toEqual(spendTx.toHex())
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
it('should throw an error when deserializing Atomic BEEF if subject transaction is missing', () => {
|
|
674
|
+
// Create Atomic BEEF data with missing subject transaction
|
|
675
|
+
const writer = new Writer()
|
|
676
|
+
// Write Atomic BEEF prefix
|
|
677
|
+
writer.writeUInt32LE(0x01010101)
|
|
678
|
+
// Write subject TXID
|
|
679
|
+
const fakeTXID = toArray('00'.repeat(32), 'hex')
|
|
680
|
+
writer.write(fakeTXID)
|
|
681
|
+
// Write empty BEEF data
|
|
682
|
+
writer.writeUInt32LE(BEEF_MAGIC) // BEEF version
|
|
683
|
+
writer.writeVarIntNum(0) // No BUMPs
|
|
684
|
+
writer.writeVarIntNum(0) // No transactions
|
|
685
|
+
|
|
686
|
+
const atomicBEEFData = writer.toArray()
|
|
687
|
+
|
|
688
|
+
expect(() => {
|
|
689
|
+
Transaction.fromAtomicBEEF(atomicBEEFData)
|
|
690
|
+
}).toThrowError('Subject transaction with TXID 0000000000000000000000000000000000000000000000000000000000000000 not found in Atomic BEEF data.')
|
|
691
|
+
})
|
|
692
|
+
|
|
693
|
+
it('should throw an error when deserializing Atomic BEEF if there are unrelated transactions', async () => {
|
|
694
|
+
// Create two unrelated transactions
|
|
695
|
+
const privateKey1 = new PrivateKey(1)
|
|
696
|
+
const publicKey1 = new Curve().g.mul(privateKey1)
|
|
697
|
+
const publicKeyHash1 = hash160(publicKey1.encode(true)) as number[]
|
|
698
|
+
const p2pkh = new P2PKH()
|
|
699
|
+
|
|
700
|
+
const sourceTx1 = new Transaction(1, [], [{
|
|
701
|
+
lockingScript: p2pkh.lock(publicKeyHash1),
|
|
702
|
+
satoshis: 10000
|
|
703
|
+
}], 0)
|
|
704
|
+
|
|
705
|
+
const spendTx1 = new Transaction(1, [{
|
|
706
|
+
sourceTransaction: sourceTx1,
|
|
707
|
+
sourceOutputIndex: 0,
|
|
708
|
+
unlockingScriptTemplate: p2pkh.unlock(privateKey1),
|
|
709
|
+
sequence: 0xffffffff
|
|
710
|
+
}], [{
|
|
711
|
+
satoshis: 9000,
|
|
712
|
+
lockingScript: p2pkh.lock(publicKeyHash1)
|
|
713
|
+
}], 0)
|
|
714
|
+
|
|
715
|
+
// Another unrelated transaction
|
|
716
|
+
const privateKey2 = new PrivateKey(2)
|
|
717
|
+
const publicKey2 = new Curve().g.mul(privateKey2)
|
|
718
|
+
const publicKeyHash2 = hash160(publicKey2.encode(true)) as number[]
|
|
719
|
+
|
|
720
|
+
const sourceTx2 = new Transaction(1, [], [{
|
|
721
|
+
lockingScript: p2pkh.lock(publicKeyHash2),
|
|
722
|
+
satoshis: 10000
|
|
723
|
+
}], 0)
|
|
724
|
+
|
|
725
|
+
// Assign merkle paths to source transactions to simulate mined transactions
|
|
726
|
+
const sourceTx1ID = sourceTx1.id('hex')
|
|
727
|
+
const sourceTx2ID = sourceTx2.id('hex')
|
|
728
|
+
sourceTx1.merklePath = new MerklePath(1000, [
|
|
729
|
+
[
|
|
730
|
+
{ offset: 0, hash: sourceTx1ID, txid: true },
|
|
731
|
+
{ offset: 1, duplicate: true }
|
|
732
|
+
]
|
|
733
|
+
])
|
|
734
|
+
sourceTx2.merklePath = new MerklePath(1001, [
|
|
735
|
+
[
|
|
736
|
+
{ offset: 0, hash: sourceTx2ID, txid: true },
|
|
737
|
+
{ offset: 1, duplicate: true }
|
|
738
|
+
]
|
|
739
|
+
])
|
|
740
|
+
|
|
741
|
+
// Sign the transaction
|
|
742
|
+
await spendTx1.fee()
|
|
743
|
+
await spendTx1.sign()
|
|
744
|
+
|
|
745
|
+
// Construct the Atomic BEEF data manually, including the unrelated transaction
|
|
746
|
+
const writer = new Writer()
|
|
747
|
+
// Write Atomic BEEF prefix
|
|
748
|
+
writer.writeUInt32LE(0x01010101)
|
|
749
|
+
// Write subject TXID (spendTx1)
|
|
750
|
+
const spendTx1ID = spendTx1.id()
|
|
751
|
+
writer.write(spendTx1ID)
|
|
752
|
+
|
|
753
|
+
// Build BEEF data
|
|
754
|
+
const beefWriter = new Writer()
|
|
755
|
+
beefWriter.writeUInt32LE(BEEF_MAGIC) // BEEF version
|
|
756
|
+
// BUMPs
|
|
757
|
+
beefWriter.writeVarIntNum(2)
|
|
758
|
+
beefWriter.write(sourceTx1.merklePath.toBinary())
|
|
759
|
+
beefWriter.write(sourceTx2.merklePath.toBinary())
|
|
760
|
+
// Transactions
|
|
761
|
+
beefWriter.writeVarIntNum(3)
|
|
762
|
+
// Transaction 1 (sourceTx1)
|
|
763
|
+
beefWriter.write(sourceTx1.toBinary())
|
|
764
|
+
beefWriter.writeUInt8(1) // hasBump
|
|
765
|
+
beefWriter.writeVarIntNum(0) // BUMP index
|
|
766
|
+
// Transaction 2 (sourceTx2) - unrelated
|
|
767
|
+
beefWriter.write(sourceTx2.toBinary())
|
|
768
|
+
beefWriter.writeUInt8(1) // hasBump
|
|
769
|
+
beefWriter.writeVarIntNum(1) // BUMP index
|
|
770
|
+
// Transaction 3 (spendTx1)
|
|
771
|
+
beefWriter.write(spendTx1.toBinary())
|
|
772
|
+
beefWriter.writeUInt8(0) // no BUMP
|
|
773
|
+
|
|
774
|
+
// Combine Atomic BEEF data
|
|
775
|
+
writer.write(beefWriter.toArray())
|
|
776
|
+
const atomicBEEFData = writer.toArray()
|
|
777
|
+
|
|
778
|
+
// Attempt to deserialize
|
|
779
|
+
expect(() => {
|
|
780
|
+
Transaction.fromAtomicBEEF(atomicBEEFData)
|
|
781
|
+
}).toThrowError(/Unrelated transaction with TXID .+ found in Atomic BEEF data./)
|
|
782
|
+
})
|
|
783
|
+
|
|
784
|
+
it('should allow selecting a specific TXID from BEEF data', async () => {
|
|
785
|
+
// Create two transactions, one depending on the other
|
|
786
|
+
const privateKey = new PrivateKey(1)
|
|
787
|
+
const publicKey = new Curve().g.mul(privateKey)
|
|
788
|
+
const publicKeyHash = hash160(publicKey.encode(true)) as number[]
|
|
789
|
+
const p2pkh = new P2PKH()
|
|
790
|
+
|
|
791
|
+
const sourceTx = new Transaction(1, [], [{
|
|
792
|
+
lockingScript: p2pkh.lock(publicKeyHash),
|
|
793
|
+
satoshis: 10000
|
|
794
|
+
}], 0)
|
|
795
|
+
|
|
796
|
+
const spendTx = new Transaction(1, [{
|
|
797
|
+
sourceTransaction: sourceTx,
|
|
798
|
+
sourceOutputIndex: 0,
|
|
799
|
+
unlockingScriptTemplate: p2pkh.unlock(privateKey),
|
|
800
|
+
sequence: 0xffffffff
|
|
801
|
+
}], [{
|
|
802
|
+
satoshis: 9000,
|
|
803
|
+
lockingScript: p2pkh.lock(publicKeyHash)
|
|
804
|
+
}], 0)
|
|
805
|
+
|
|
806
|
+
// Sign transactions
|
|
807
|
+
await spendTx.fee()
|
|
808
|
+
await spendTx.sign()
|
|
809
|
+
|
|
810
|
+
// Assign merkle path to source transaction
|
|
811
|
+
const sourceTxID = sourceTx.id('hex')
|
|
812
|
+
sourceTx.merklePath = new MerklePath(1000, [
|
|
813
|
+
[
|
|
814
|
+
{ offset: 0, hash: sourceTxID, txid: true },
|
|
815
|
+
{ offset: 1, duplicate: true }
|
|
816
|
+
]
|
|
817
|
+
])
|
|
818
|
+
|
|
819
|
+
// Serialize to BEEF
|
|
820
|
+
const beefData = spendTx.toBEEF()
|
|
821
|
+
// Get TXIDs
|
|
822
|
+
const spendTxID = spendTx.id('hex')
|
|
823
|
+
|
|
824
|
+
// Deserialize the source transaction from BEEF data
|
|
825
|
+
const deserializedSourceTx = Transaction.fromBEEF(beefData, sourceTxID)
|
|
826
|
+
expect(deserializedSourceTx.id('hex')).toEqual(sourceTxID)
|
|
827
|
+
|
|
828
|
+
// Deserialize the spend transaction from BEEF data
|
|
829
|
+
const deserializedSpendTx = Transaction.fromBEEF(beefData, spendTxID)
|
|
830
|
+
expect(deserializedSpendTx.id('hex')).toEqual(spendTxID)
|
|
831
|
+
|
|
832
|
+
// Attempt to deserialize a non-existent transaction
|
|
833
|
+
expect(() => {
|
|
834
|
+
Transaction.fromBEEF(beefData, '00'.repeat(32))
|
|
835
|
+
}).toThrowError('Transaction with TXID 0000000000000000000000000000000000000000000000000000000000000000 not found in BEEF data.')
|
|
836
|
+
})
|
|
837
|
+
})
|
|
619
838
|
})
|