@bsv/sdk 1.2.3 → 1.2.4
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/auth/Certificate.js +1 -1
- package/dist/cjs/src/auth/Certificate.js.map +1 -1
- package/dist/cjs/src/overlay-tools/LookupResolver.js.map +1 -1
- package/dist/cjs/src/overlay-tools/OverlayAdminTokenTemplate.js.map +1 -1
- package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js +1 -1
- package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js.map +1 -1
- package/dist/cjs/src/transaction/Beef.js +19 -10
- package/dist/cjs/src/transaction/Beef.js.map +1 -1
- package/dist/cjs/src/transaction/Transaction.js +38 -6
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/src/wallet/CachedKeyDeriver.js.map +1 -1
- package/dist/cjs/src/wallet/KeyDeriver.js.map +1 -1
- package/dist/cjs/src/wallet/ProtoWallet.js.map +1 -1
- package/dist/cjs/src/wallet/WalletClient.js.map +1 -1
- package/dist/cjs/src/wallet/WalletError.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/auth/Certificate.js +1 -1
- package/dist/esm/src/auth/Certificate.js.map +1 -1
- package/dist/esm/src/overlay-tools/LookupResolver.js.map +1 -1
- package/dist/esm/src/overlay-tools/OverlayAdminTokenTemplate.js.map +1 -1
- package/dist/esm/src/overlay-tools/SHIPBroadcaster.js +1 -1
- package/dist/esm/src/overlay-tools/SHIPBroadcaster.js.map +1 -1
- package/dist/esm/src/transaction/Beef.js +19 -10
- package/dist/esm/src/transaction/Beef.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +38 -6
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/src/wallet/CachedKeyDeriver.js.map +1 -1
- package/dist/esm/src/wallet/KeyDeriver.js.map +1 -1
- package/dist/esm/src/wallet/ProtoWallet.js.map +1 -1
- package/dist/esm/src/wallet/WalletClient.js.map +1 -1
- package/dist/esm/src/wallet/WalletError.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/auth/Certificate.d.ts.map +1 -1
- package/dist/types/src/overlay-tools/LookupResolver.d.ts.map +1 -1
- package/dist/types/src/overlay-tools/OverlayAdminTokenTemplate.d.ts.map +1 -1
- package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts.map +1 -1
- package/dist/types/src/transaction/Beef.d.ts.map +1 -1
- package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
- package/dist/types/src/wallet/CachedKeyDeriver.d.ts.map +1 -1
- package/dist/types/src/wallet/KeyDeriver.d.ts.map +1 -1
- package/dist/types/src/wallet/ProtoWallet.d.ts.map +1 -1
- package/dist/types/src/wallet/Wallet.interfaces.d.ts +11 -11
- package/dist/types/src/wallet/Wallet.interfaces.d.ts.map +1 -1
- package/dist/types/src/wallet/WalletClient.d.ts.map +1 -1
- package/dist/types/src/wallet/WalletError.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/package.json +1 -1
- package/src/auth/Certificate.ts +9 -9
- package/src/auth/index.ts +1 -1
- package/src/overlay-tools/LookupResolver.ts +7 -7
- package/src/overlay-tools/OverlayAdminTokenTemplate.ts +4 -4
- package/src/overlay-tools/SHIPBroadcaster.ts +9 -9
- package/src/overlay-tools/index.ts +1 -1
- package/src/transaction/Beef.ts +33 -48
- package/src/transaction/BeefParty.ts +4 -4
- package/src/transaction/BeefTx.ts +1 -1
- package/src/transaction/Transaction.ts +38 -10
- package/src/transaction/__tests/Transaction.test.ts +133 -4
- package/src/wallet/CachedKeyDeriver.ts +10 -10
- package/src/wallet/KeyDeriver.ts +8 -8
- package/src/wallet/ProtoWallet.ts +176 -176
- package/src/wallet/Wallet.interfaces.ts +19 -19
- package/src/wallet/WalletClient.ts +30 -30
- package/src/wallet/WalletError.ts +2 -2
package/package.json
CHANGED
package/src/auth/Certificate.ts
CHANGED
|
@@ -54,14 +54,14 @@ export default class Certificate {
|
|
|
54
54
|
* @param {Record<CertificateFieldNameUnder50Bytes, string>} fields - All the fields present in the certificate.
|
|
55
55
|
* @param {HexString} signature - Certificate signature by the certifier's private key, DER encoded hex string.
|
|
56
56
|
*/
|
|
57
|
-
constructor(
|
|
57
|
+
constructor (
|
|
58
58
|
type: Base64String,
|
|
59
59
|
serialNumber: Base64String,
|
|
60
60
|
subject: PubKeyHex,
|
|
61
61
|
certifier: PubKeyHex,
|
|
62
62
|
revocationOutpoint: OutpointString,
|
|
63
63
|
fields: Record<CertificateFieldNameUnder50Bytes, string>,
|
|
64
|
-
signature?: HexString
|
|
64
|
+
signature?: HexString
|
|
65
65
|
) {
|
|
66
66
|
this.type = type
|
|
67
67
|
this.serialNumber = serialNumber
|
|
@@ -78,7 +78,7 @@ export default class Certificate {
|
|
|
78
78
|
* @param {boolean} [includeSignature=true] - Whether to include the signature in the serialization.
|
|
79
79
|
* @returns {number[]} - The serialized certificate in binary format.
|
|
80
80
|
*/
|
|
81
|
-
toBin(includeSignature: boolean = true): number[] {
|
|
81
|
+
toBin (includeSignature: boolean = true): number[] {
|
|
82
82
|
const writer = new Utils.Writer()
|
|
83
83
|
|
|
84
84
|
// Write type (Base64String, 32 bytes)
|
|
@@ -134,7 +134,7 @@ export default class Certificate {
|
|
|
134
134
|
* @param {number[]} bin - The binary data representing the certificate.
|
|
135
135
|
* @returns {Certificate} - The deserialized Certificate object.
|
|
136
136
|
*/
|
|
137
|
-
static fromBin(bin: number[]): Certificate {
|
|
137
|
+
static fromBin (bin: number[]): Certificate {
|
|
138
138
|
const reader = new Utils.Reader(bin)
|
|
139
139
|
|
|
140
140
|
// Read type
|
|
@@ -166,7 +166,7 @@ export default class Certificate {
|
|
|
166
166
|
// Field name
|
|
167
167
|
const fieldNameLength = reader.readVarIntNum()
|
|
168
168
|
const fieldNameBytes = reader.read(fieldNameLength)
|
|
169
|
-
const fieldName = Utils.toUTF8(fieldNameBytes)
|
|
169
|
+
const fieldName = Utils.toUTF8(fieldNameBytes)
|
|
170
170
|
|
|
171
171
|
// Field value
|
|
172
172
|
const fieldValueLength = reader.readVarIntNum()
|
|
@@ -177,7 +177,7 @@ export default class Certificate {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
// Read signature if present
|
|
180
|
-
let signature: string | undefined
|
|
180
|
+
let signature: string | undefined
|
|
181
181
|
if (!reader.eof()) {
|
|
182
182
|
const signatureLength = reader.readVarIntNum()
|
|
183
183
|
const signatureBytes = reader.read(signatureLength)
|
|
@@ -200,7 +200,7 @@ export default class Certificate {
|
|
|
200
200
|
*
|
|
201
201
|
* @returns {Promise<boolean>} - A promise that resolves to true if the signature is valid.
|
|
202
202
|
*/
|
|
203
|
-
async verify(): Promise<boolean> {
|
|
203
|
+
async verify (): Promise<boolean> {
|
|
204
204
|
// A verifier can be any wallet capable of verifying signatures
|
|
205
205
|
const verifier = new ProtoWallet('anyone')
|
|
206
206
|
const verificationData = this.toBin(false) // Exclude the signature from the verification data
|
|
@@ -221,7 +221,7 @@ export default class Certificate {
|
|
|
221
221
|
* @param {Wallet} certifier - The wallet representing the certifier.
|
|
222
222
|
* @returns {Promise<void>}
|
|
223
223
|
*/
|
|
224
|
-
async sign(certifier: Wallet): Promise<void> {
|
|
224
|
+
async sign (certifier: Wallet): Promise<void> {
|
|
225
225
|
const preimage = this.toBin(false) // Exclude the signature when signing
|
|
226
226
|
const { signature } = await certifier.createSignature({
|
|
227
227
|
data: preimage,
|
|
@@ -230,4 +230,4 @@ export default class Certificate {
|
|
|
230
230
|
})
|
|
231
231
|
this.signature = Utils.toHex(signature)
|
|
232
232
|
}
|
|
233
|
-
}
|
|
233
|
+
}
|
package/src/auth/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default as Certificate } from './Certificate.js'
|
|
1
|
+
export { default as Certificate } from './Certificate.js'
|
|
@@ -70,7 +70,7 @@ export interface OverlayLookupFacilitator {
|
|
|
70
70
|
* @param url - Overlay Service URL to send the lookup question to.
|
|
71
71
|
* @param question - Lookup question to find an answer to.
|
|
72
72
|
* @param timeout - Specifics how long to wait for a lookup answer in milliseconds.
|
|
73
|
-
* @returns
|
|
73
|
+
* @returns
|
|
74
74
|
*/
|
|
75
75
|
lookup: (url: string, question: LookupQuestion, timeout?: number) => Promise<LookupAnswer>
|
|
76
76
|
}
|
|
@@ -78,11 +78,11 @@ export interface OverlayLookupFacilitator {
|
|
|
78
78
|
export class HTTPSOverlayLookupFacilitator implements OverlayLookupFacilitator {
|
|
79
79
|
fetchClient: typeof fetch
|
|
80
80
|
|
|
81
|
-
constructor(httpClient = fetch) {
|
|
81
|
+
constructor (httpClient = fetch) {
|
|
82
82
|
this.fetchClient = httpClient
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
async lookup(url: string, question: LookupQuestion, timeout: number = 5000): Promise<LookupAnswer> {
|
|
85
|
+
async lookup (url: string, question: LookupQuestion, timeout: number = 5000): Promise<LookupAnswer> {
|
|
86
86
|
if (!url.startsWith('https:')) {
|
|
87
87
|
throw new Error('HTTPS facilitator can only use URLs that start with "https:"')
|
|
88
88
|
}
|
|
@@ -98,7 +98,7 @@ export class HTTPSOverlayLookupFacilitator implements OverlayLookupFacilitator {
|
|
|
98
98
|
body: JSON.stringify({ service: question.service, query: question.query })
|
|
99
99
|
})
|
|
100
100
|
|
|
101
|
-
const response: Response = await Promise.race([fetchPromise, timeoutPromise]) as
|
|
101
|
+
const response: Response = await Promise.race([fetchPromise, timeoutPromise]) as Response
|
|
102
102
|
|
|
103
103
|
if (response.ok) {
|
|
104
104
|
return await response.json()
|
|
@@ -117,7 +117,7 @@ export default class LookupResolver {
|
|
|
117
117
|
private readonly hostOverrides: Record<string, string[]>
|
|
118
118
|
private readonly additionalHosts: Record<string, string[]>
|
|
119
119
|
|
|
120
|
-
constructor(config?: LookupResolverConfig) {
|
|
120
|
+
constructor (config?: LookupResolverConfig) {
|
|
121
121
|
const { facilitator, slapTrackers, hostOverrides, additionalHosts } = config ?? {} as LookupResolverConfig
|
|
122
122
|
this.facilitator = facilitator ?? new HTTPSOverlayLookupFacilitator()
|
|
123
123
|
this.slapTrackers = slapTrackers ?? DEFAULT_SLAP_TRACKERS
|
|
@@ -128,7 +128,7 @@ export default class LookupResolver {
|
|
|
128
128
|
/**
|
|
129
129
|
* Given a LookupQuestion, returns a LookupAnswer. Aggregates across multiple services and supports resiliency.
|
|
130
130
|
*/
|
|
131
|
-
async query(question: LookupQuestion, timeout?: number): Promise<LookupAnswer> {
|
|
131
|
+
async query (question: LookupQuestion, timeout?: number): Promise<LookupAnswer> {
|
|
132
132
|
let competentHosts: string[] = []
|
|
133
133
|
if (question.service === 'ls_slap') {
|
|
134
134
|
competentHosts = this.slapTrackers
|
|
@@ -196,7 +196,7 @@ export default class LookupResolver {
|
|
|
196
196
|
* @param service Service for which competent hosts are to be returned
|
|
197
197
|
* @returns Array of hosts competent for resolving queries
|
|
198
198
|
*/
|
|
199
|
-
private async findCompetentHosts(service: string): Promise<string[]> {
|
|
199
|
+
private async findCompetentHosts (service: string): Promise<string[]> {
|
|
200
200
|
const query: LookupQuestion = {
|
|
201
201
|
service: 'ls_slap',
|
|
202
202
|
query: {
|
|
@@ -15,7 +15,7 @@ export default class OverlayAdminTokenTemplate implements ScriptTemplate {
|
|
|
15
15
|
* @param script Locking script comprising a SHIP or SLAP token to decode
|
|
16
16
|
* @returns Decoded SHIP or SLAP advertisement
|
|
17
17
|
*/
|
|
18
|
-
static decode(script: LockingScript): { protocol: 'SHIP' | 'SLAP', identityKey: string, domain: string, topicOrService: string } {
|
|
18
|
+
static decode (script: LockingScript): { protocol: 'SHIP' | 'SLAP', identityKey: string, domain: string, topicOrService: string } {
|
|
19
19
|
const result = PushDrop.decode(script)
|
|
20
20
|
if (result.fields.length < 4) {
|
|
21
21
|
throw new Error('Invalid SHIP/SLAP advertisement!')
|
|
@@ -39,7 +39,7 @@ export default class OverlayAdminTokenTemplate implements ScriptTemplate {
|
|
|
39
39
|
* Constructs a new Overlay Admin template instance
|
|
40
40
|
* @param wallet Wallet to use for locking and unlocking
|
|
41
41
|
*/
|
|
42
|
-
constructor(wallet: Wallet) {
|
|
42
|
+
constructor (wallet: Wallet) {
|
|
43
43
|
this.pushDrop = new PushDrop(wallet)
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -50,7 +50,7 @@ export default class OverlayAdminTokenTemplate implements ScriptTemplate {
|
|
|
50
50
|
* @param topicOrService Topic or service to advertise
|
|
51
51
|
* @returns Locking script comprising the advertisement token
|
|
52
52
|
*/
|
|
53
|
-
async lock(protocol: 'SHIP' | 'SLAP', domain: string, topicOrService: string): Promise<LockingScript> {
|
|
53
|
+
async lock (protocol: 'SHIP' | 'SLAP', domain: string, topicOrService: string): Promise<LockingScript> {
|
|
54
54
|
const { publicKey: identityKey } = await this.pushDrop.wallet.getPublicKey({ identityKey: true })
|
|
55
55
|
return await this.pushDrop.lock(
|
|
56
56
|
[
|
|
@@ -70,7 +70,7 @@ export default class OverlayAdminTokenTemplate implements ScriptTemplate {
|
|
|
70
70
|
* @param protocol SHIP or SLAP, depending on the token to unlock
|
|
71
71
|
* @returns Script unlocker capable of unlocking the advertisement token
|
|
72
72
|
*/
|
|
73
|
-
unlock(protocol: 'SHIP' | 'SLAP'): {
|
|
73
|
+
unlock (protocol: 'SHIP' | 'SLAP'): {
|
|
74
74
|
sign: (tx: Transaction, inputIndex: number) => Promise<UnlockingScript>
|
|
75
75
|
estimateLength: (tx: Transaction, inputIndex: number) => Promise<number>
|
|
76
76
|
} {
|
|
@@ -61,11 +61,11 @@ const MAX_SHIP_QUERY_TIMEOUT = 1000
|
|
|
61
61
|
export class HTTPSOverlayBroadcastFacilitator implements OverlayBroadcastFacilitator {
|
|
62
62
|
httpClient: typeof fetch
|
|
63
63
|
|
|
64
|
-
constructor(httpClient = fetch) {
|
|
64
|
+
constructor (httpClient = fetch) {
|
|
65
65
|
this.httpClient = httpClient
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
async send(url: string, taggedBEEF: TaggedBEEF): Promise<STEAK> {
|
|
68
|
+
async send (url: string, taggedBEEF: TaggedBEEF): Promise<STEAK> {
|
|
69
69
|
if (!url.startsWith('https:')) {
|
|
70
70
|
throw new Error('HTTPS facilitator can only use URLs that start with "https:"')
|
|
71
71
|
}
|
|
@@ -102,7 +102,7 @@ export default class SHIPCast implements Broadcaster {
|
|
|
102
102
|
* @param {string[]} topics - The list of SHIP topic names where transactions are to be sent.
|
|
103
103
|
* @param {SHIPBroadcasterConfig} config - Configuration options for the SHIP broadcaster.
|
|
104
104
|
*/
|
|
105
|
-
constructor(topics: string[], config?: SHIPBroadcasterConfig) {
|
|
105
|
+
constructor (topics: string[], config?: SHIPBroadcasterConfig) {
|
|
106
106
|
if (topics.length === 0) {
|
|
107
107
|
throw new Error('At least one topic is required for broadcast.')
|
|
108
108
|
}
|
|
@@ -129,7 +129,7 @@ export default class SHIPCast implements Broadcaster {
|
|
|
129
129
|
* @param {Transaction} tx - The transaction to be sent.
|
|
130
130
|
* @returns {Promise<BroadcastResponse | BroadcastFailure>} A promise that resolves to either a success or failure response.
|
|
131
131
|
*/
|
|
132
|
-
async broadcast(tx: Transaction): Promise<BroadcastResponse | BroadcastFailure> {
|
|
132
|
+
async broadcast (tx: Transaction): Promise<BroadcastResponse | BroadcastFailure> {
|
|
133
133
|
let beef: number[]
|
|
134
134
|
try {
|
|
135
135
|
beef = tx.toBEEF()
|
|
@@ -271,7 +271,7 @@ export default class SHIPCast implements Broadcaster {
|
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
private checkAcknowledgmentFromAllHosts(hostAcknowledgments: Record<string, Set<string>>, requiredTopics: string[], require: 'all' | 'any'): boolean {
|
|
274
|
+
private checkAcknowledgmentFromAllHosts (hostAcknowledgments: Record<string, Set<string>>, requiredTopics: string[], require: 'all' | 'any'): boolean {
|
|
275
275
|
for (const acknowledgedTopics of Object.values(hostAcknowledgments)) {
|
|
276
276
|
if (require === 'all') {
|
|
277
277
|
for (const topic of requiredTopics) {
|
|
@@ -295,7 +295,7 @@ export default class SHIPCast implements Broadcaster {
|
|
|
295
295
|
return true
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
private checkAcknowledgmentFromAnyHost(hostAcknowledgments: Record<string, Set<string>>, requiredTopics: string[], require: 'all' | 'any'): boolean {
|
|
298
|
+
private checkAcknowledgmentFromAnyHost (hostAcknowledgments: Record<string, Set<string>>, requiredTopics: string[], require: 'all' | 'any'): boolean {
|
|
299
299
|
if (require === 'all') {
|
|
300
300
|
// All required topics must be acknowledged by at least one host
|
|
301
301
|
for (const acknowledgedTopics of Object.values(hostAcknowledgments)) {
|
|
@@ -324,7 +324,7 @@ export default class SHIPCast implements Broadcaster {
|
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
private checkAcknowledgmentFromSpecificHosts(hostAcknowledgments: Record<string, Set<string>>, requirements: Record<string, 'all' | 'any' | string[]>): boolean {
|
|
327
|
+
private checkAcknowledgmentFromSpecificHosts (hostAcknowledgments: Record<string, Set<string>>, requirements: Record<string, 'all' | 'any' | string[]>): boolean {
|
|
328
328
|
for (const [host, requiredTopicsOrAllAny] of Object.entries(requirements)) {
|
|
329
329
|
const acknowledgedTopics = hostAcknowledgments[host]
|
|
330
330
|
if (!acknowledgedTopics) {
|
|
@@ -370,7 +370,7 @@ export default class SHIPCast implements Broadcaster {
|
|
|
370
370
|
*
|
|
371
371
|
* @returns A mapping of URLs for hosts interested in this transaction. Keys are URLs, values are which of our topics the specific host cares about.
|
|
372
372
|
*/
|
|
373
|
-
private async findInterestedHosts(): Promise<Record<string, Set<string>>> {
|
|
373
|
+
private async findInterestedHosts (): Promise<Record<string, Set<string>>> {
|
|
374
374
|
// TODO: cache the list of interested hosts to avoid spamming SHIP trackers.
|
|
375
375
|
// TODO: Monetize the operation of the SHIP tracker system.
|
|
376
376
|
// TODO: Cache ship/slap lookup with expiry (every 5min)
|
|
@@ -381,7 +381,7 @@ export default class SHIPCast implements Broadcaster {
|
|
|
381
381
|
service: 'ls_ship',
|
|
382
382
|
query: {
|
|
383
383
|
topics: this.topics
|
|
384
|
-
}
|
|
384
|
+
}
|
|
385
385
|
}, MAX_SHIP_QUERY_TIMEOUT)
|
|
386
386
|
if (answer.type !== 'output-list') {
|
|
387
387
|
throw new Error('SHIP answer is not an output list.')
|
|
@@ -2,4 +2,4 @@ export * from './LookupResolver.js'
|
|
|
2
2
|
export * from './SHIPBroadcaster.js'
|
|
3
3
|
export { default as OverlayAdminTokenTemplate } from './OverlayAdminTokenTemplate.js'
|
|
4
4
|
export { default as LookupResolver } from './LookupResolver.js'
|
|
5
|
-
export { default as SHIPBroadcaster } from './SHIPBroadcaster.js'
|
|
5
|
+
export { default as SHIPBroadcaster } from './SHIPBroadcaster.js'
|
package/src/transaction/Beef.ts
CHANGED
|
@@ -17,12 +17,12 @@ export type BeefVersion = undefined | 'V1' | 'V2'
|
|
|
17
17
|
*
|
|
18
18
|
* BUMP standard: BRC-74: BSV Unified Merkle Path (BUMP) Format
|
|
19
19
|
* https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0074.md
|
|
20
|
-
*
|
|
20
|
+
*
|
|
21
21
|
* BRC-95: Atomic BEEF Transactions
|
|
22
22
|
* https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0095.md
|
|
23
|
-
*
|
|
23
|
+
*
|
|
24
24
|
* The Atomic BEEF format is supported by the binary deserialization static method `fromBinary`.
|
|
25
|
-
*
|
|
25
|
+
*
|
|
26
26
|
* BRC-96: BEEF V2, Txid Only Extension
|
|
27
27
|
* https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0096.md
|
|
28
28
|
*
|
|
@@ -57,7 +57,7 @@ export type BeefVersion = undefined | 'V1' | 'V2'
|
|
|
57
57
|
*
|
|
58
58
|
* A valid `Beef` is only required when sent to a party with no shared history,
|
|
59
59
|
* such as a transaction processor.
|
|
60
|
-
*
|
|
60
|
+
*
|
|
61
61
|
* IMPORTANT NOTE:
|
|
62
62
|
* It is fundamental to the BEEF value proposition that only valid transactions and valid
|
|
63
63
|
* merkle path (BUMP) data be added to it. Merging invalid data breaks the `verify` and `isValid`
|
|
@@ -108,14 +108,13 @@ export class Beef {
|
|
|
108
108
|
/**
|
|
109
109
|
* Finds a Transaction in this `Beef`
|
|
110
110
|
* and adds any missing input SourceTransactions from this `Beef`.
|
|
111
|
-
*
|
|
111
|
+
*
|
|
112
112
|
* The result is suitable for signing.
|
|
113
|
-
*
|
|
113
|
+
*
|
|
114
114
|
* @param txid The id of the target transaction.
|
|
115
115
|
* @returns Transaction with all available input `SourceTransaction`s from this Beef.
|
|
116
116
|
*/
|
|
117
117
|
findTransactionForSigning (txid: string): Transaction | undefined {
|
|
118
|
-
|
|
119
118
|
const beefTx = this.findTxid(txid)
|
|
120
119
|
if (!beefTx) return undefined
|
|
121
120
|
|
|
@@ -130,24 +129,22 @@ export class Beef {
|
|
|
130
129
|
|
|
131
130
|
return beefTx.tx
|
|
132
131
|
}
|
|
132
|
+
|
|
133
133
|
/**
|
|
134
134
|
* Builds the proof tree rooted at a specific `Transaction`.
|
|
135
|
-
*
|
|
135
|
+
*
|
|
136
136
|
* To succeed, the Beef must contain all the required transaction and merkle path data.
|
|
137
|
-
*
|
|
137
|
+
*
|
|
138
138
|
* @param txid The id of the target transaction.
|
|
139
139
|
* @returns Transaction with input `SourceTransaction` and `MerklePath` populated from this Beef.
|
|
140
140
|
*/
|
|
141
141
|
findAtomicTransaction (txid: string): Transaction | undefined {
|
|
142
|
-
|
|
143
142
|
const beefTx = this.findTxid(txid)
|
|
144
143
|
if (!beefTx) return undefined
|
|
145
144
|
|
|
146
145
|
const addInputProof = (beef: Beef, tx: Transaction) => {
|
|
147
146
|
const mp = beef.findBump(tx.id('hex'))
|
|
148
|
-
if (mp)
|
|
149
|
-
tx.merklePath = mp
|
|
150
|
-
else {
|
|
147
|
+
if (mp) { tx.merklePath = mp } else {
|
|
151
148
|
for (const i of tx.inputs) {
|
|
152
149
|
if (!i.sourceTransaction) {
|
|
153
150
|
const itx = beef.findTxid(i.sourceTXID)
|
|
@@ -159,8 +156,7 @@ export class Beef {
|
|
|
159
156
|
const mp = beef.findBump(i.sourceTransaction.id('hex'))
|
|
160
157
|
if (mp) {
|
|
161
158
|
i.sourceTransaction.merklePath = mp
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
159
|
+
} else {
|
|
164
160
|
addInputProof(beef, i.sourceTransaction)
|
|
165
161
|
}
|
|
166
162
|
}
|
|
@@ -397,7 +393,7 @@ export class Beef {
|
|
|
397
393
|
|
|
398
394
|
/**
|
|
399
395
|
* Serializes this data to `writer`
|
|
400
|
-
* @param writer
|
|
396
|
+
* @param writer
|
|
401
397
|
*/
|
|
402
398
|
toWriter (writer: Writer) {
|
|
403
399
|
writer.writeUInt32LE(this.magic)
|
|
@@ -425,20 +421,18 @@ export class Beef {
|
|
|
425
421
|
|
|
426
422
|
/**
|
|
427
423
|
* Serialize this Beef as AtomicBEEF.
|
|
428
|
-
*
|
|
424
|
+
*
|
|
429
425
|
* `txid` must exist and be the last transaction
|
|
430
426
|
* in sorted (dependency) order.
|
|
431
|
-
*
|
|
432
|
-
* @param txid
|
|
427
|
+
*
|
|
428
|
+
* @param txid
|
|
433
429
|
* @returns serialized contents of this Beef with AtomicBEEF prefix.
|
|
434
430
|
*/
|
|
435
|
-
toBinaryAtomic(txid: string) {
|
|
431
|
+
toBinaryAtomic (txid: string) {
|
|
436
432
|
this.sortTxs()
|
|
437
433
|
const tx = this.findTxid(txid)
|
|
438
|
-
if (!tx)
|
|
439
|
-
|
|
440
|
-
if (this.txs[this.txs.length - 1] !== tx)
|
|
441
|
-
throw new Error(`${txid} is not the last transaction in this Beef`)
|
|
434
|
+
if (!tx) { throw new Error(`${txid} does not exist in this Beef`) }
|
|
435
|
+
if (this.txs[this.txs.length - 1] !== tx) { throw new Error(`${txid} is not the last transaction in this Beef`) }
|
|
442
436
|
const writer = new Writer()
|
|
443
437
|
writer.writeUInt32LE(ATOMIC_BEEF)
|
|
444
438
|
writer.write(toArray(txid, 'hex'))
|
|
@@ -456,7 +450,7 @@ export class Beef {
|
|
|
456
450
|
|
|
457
451
|
static fromReader (br: Reader): Beef {
|
|
458
452
|
let version = br.readUInt32LE()
|
|
459
|
-
let atomicTxid: string | undefined
|
|
453
|
+
let atomicTxid: string | undefined
|
|
460
454
|
if (version === ATOMIC_BEEF) {
|
|
461
455
|
// Skip the txid and re-read the BEEF version
|
|
462
456
|
atomicTxid = toHex(br.read(32))
|
|
@@ -527,19 +521,18 @@ export class Beef {
|
|
|
527
521
|
* - Oldest Tx Anchored by Path
|
|
528
522
|
* - Newer Txs depending on Older parents
|
|
529
523
|
* - Newest Tx
|
|
530
|
-
*
|
|
524
|
+
*
|
|
531
525
|
* with proof (MerklePath) last, longest chain of dependencies first
|
|
532
|
-
*
|
|
526
|
+
*
|
|
533
527
|
* @returns `{ missingInputs, notValid, valid, withMissingInputs }`
|
|
534
528
|
*/
|
|
535
|
-
sortTxs ()
|
|
536
|
-
:
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
} {
|
|
529
|
+
sortTxs (): {
|
|
530
|
+
missingInputs: string[]
|
|
531
|
+
notValid: string[]
|
|
532
|
+
valid: string[]
|
|
533
|
+
withMissingInputs: string[]
|
|
534
|
+
txidOnly: string[]
|
|
535
|
+
} {
|
|
543
536
|
// Hashtable of valid txids (with proof or all inputs chain to proof)
|
|
544
537
|
const validTxids: Record<string, boolean> = {}
|
|
545
538
|
|
|
@@ -560,10 +553,7 @@ export class Beef {
|
|
|
560
553
|
if (tx.isValid) {
|
|
561
554
|
validTxids[tx.txid] = true
|
|
562
555
|
result.push(tx)
|
|
563
|
-
} else if (tx.isTxidOnly)
|
|
564
|
-
txidOnly.push(tx)
|
|
565
|
-
else
|
|
566
|
-
queue.push(tx)
|
|
556
|
+
} else if (tx.isTxidOnly) { txidOnly.push(tx) } else { queue.push(tx) }
|
|
567
557
|
}
|
|
568
558
|
|
|
569
559
|
// Hashtable of unknown input txids used to fund transactions without their own proof.
|
|
@@ -587,10 +577,7 @@ export class Beef {
|
|
|
587
577
|
}
|
|
588
578
|
}
|
|
589
579
|
}
|
|
590
|
-
if (hasMissingInput)
|
|
591
|
-
txsMissingInputs.push(tx)
|
|
592
|
-
else
|
|
593
|
-
queue.push(tx)
|
|
580
|
+
if (hasMissingInput) { txsMissingInputs.push(tx) } else { queue.push(tx) }
|
|
594
581
|
}
|
|
595
582
|
|
|
596
583
|
// As long as we have unsorted transactions...
|
|
@@ -601,11 +588,9 @@ export class Beef {
|
|
|
601
588
|
if (tx.inputTxids.every(txid => validTxids[txid])) {
|
|
602
589
|
validTxids[tx.txid] = true
|
|
603
590
|
result.push(tx)
|
|
604
|
-
} else
|
|
605
|
-
queue.push(tx)
|
|
591
|
+
} else { queue.push(tx) }
|
|
606
592
|
}
|
|
607
|
-
if (oldQueue.length === queue.length)
|
|
608
|
-
break;
|
|
593
|
+
if (oldQueue.length === queue.length) { break }
|
|
609
594
|
}
|
|
610
595
|
|
|
611
596
|
// transactions that don't have proofs and don't chain to proofs
|
|
@@ -652,7 +637,7 @@ export class Beef {
|
|
|
652
637
|
/**
|
|
653
638
|
* @returns array of transaction txids that either have a proof or whose inputs chain back to a proven transaction.
|
|
654
639
|
*/
|
|
655
|
-
getValidTxids()
|
|
640
|
+
getValidTxids (): string[] {
|
|
656
641
|
const r = this.sortTxs()
|
|
657
642
|
return r.valid
|
|
658
643
|
}
|
|
@@ -94,13 +94,13 @@ export class BeefParty extends Beef {
|
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
96
|
* Merge a `beef` received from a specific `party`.
|
|
97
|
-
*
|
|
97
|
+
*
|
|
98
98
|
* Updates this `BeefParty` to track all the txids
|
|
99
99
|
* corresponding to transactions for which `party`
|
|
100
100
|
* has raw transaction and validity proof data.
|
|
101
|
-
*
|
|
102
|
-
* @param party
|
|
103
|
-
* @param beef
|
|
101
|
+
*
|
|
102
|
+
* @param party
|
|
103
|
+
* @param beef
|
|
104
104
|
*/
|
|
105
105
|
mergeBeefFromParty (party: string, beef: number[] | Beef) {
|
|
106
106
|
const b: Beef = Array.isArray(beef) ? Beef.fromBinary(beef) : beef
|
|
@@ -140,9 +140,9 @@ export default class Transaction {
|
|
|
140
140
|
const validTxids = new Set<string>()
|
|
141
141
|
// All BUMP level 0 hashes are valid.
|
|
142
142
|
for (const bump of BUMPs) {
|
|
143
|
-
for (const n of bump.path[0])
|
|
144
|
-
if (n.hash)
|
|
145
|
-
|
|
143
|
+
for (const n of bump.path[0]) {
|
|
144
|
+
if (n.hash) { validTxids.add(n.hash) }
|
|
145
|
+
}
|
|
146
146
|
}
|
|
147
147
|
// To keep track of which transactions were used.
|
|
148
148
|
const unusedTxTxids = new Set<string>()
|
|
@@ -524,17 +524,47 @@ export default class Transaction {
|
|
|
524
524
|
}
|
|
525
525
|
|
|
526
526
|
// Distribute change among change outputs
|
|
527
|
+
let distributedChange = 0
|
|
527
528
|
if (changeDistribution === 'random') {
|
|
528
|
-
//
|
|
529
|
-
|
|
529
|
+
// Implement Benford's Law distribution for change outputs
|
|
530
|
+
const changeOutputs = this.outputs.filter(out => out.change)
|
|
531
|
+
let changeToUse = change
|
|
532
|
+
|
|
533
|
+
// Helper function to generate a number approximating Benford's Law
|
|
534
|
+
const benfordNumber = (min: number, max: number): number => {
|
|
535
|
+
const d = Math.floor(Math.random() * 9) + 1
|
|
536
|
+
return Math.floor(min + (max - min) * Math.log10(1 + 1 / d) / Math.log10(10))
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const benfordNumbers = Array(changeOutputs.length).fill(1)
|
|
540
|
+
changeToUse -= changeOutputs.length
|
|
541
|
+
distributedChange += changeOutputs.length
|
|
542
|
+
for (let i = 0; i < changeOutputs.length - 1; i++) {
|
|
543
|
+
const portion = benfordNumber(0, changeToUse)
|
|
544
|
+
benfordNumbers[i] += portion
|
|
545
|
+
distributedChange += portion
|
|
546
|
+
changeToUse -= portion
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
for (let i = 0; i < this.outputs.length; i++) {
|
|
550
|
+
if (this.outputs[i].change) {
|
|
551
|
+
const t = benfordNumbers.shift()
|
|
552
|
+
this.outputs[i].satoshis = t
|
|
553
|
+
}
|
|
554
|
+
}
|
|
530
555
|
} else if (changeDistribution === 'equal') {
|
|
531
556
|
const perOutput = Math.floor(change / changeCount)
|
|
532
557
|
for (const out of this.outputs) {
|
|
533
558
|
if (out.change) {
|
|
559
|
+
distributedChange += perOutput
|
|
534
560
|
out.satoshis = perOutput
|
|
535
561
|
}
|
|
536
562
|
}
|
|
537
563
|
}
|
|
564
|
+
// if there's any remaining change, add it to the last output
|
|
565
|
+
if (distributedChange < change) {
|
|
566
|
+
this.outputs[this.outputs.length - 1].satoshis += (change - distributedChange)
|
|
567
|
+
}
|
|
538
568
|
}
|
|
539
569
|
|
|
540
570
|
/**
|
|
@@ -868,13 +898,14 @@ export default class Transaction {
|
|
|
868
898
|
|
|
869
899
|
/**
|
|
870
900
|
* Serializes this transaction, together with its inputs and the respective merkle proofs, into the BEEF (BRC-62) format. This enables efficient verification of its compliance with the rules of SPV.
|
|
871
|
-
*
|
|
901
|
+
*
|
|
872
902
|
* @param allowPartial If true, error will not be thrown if there are any missing sourceTransactions.
|
|
873
903
|
*
|
|
874
904
|
* @returns The serialized BEEF structure
|
|
875
905
|
* @throws Error if there are any missing sourceTransactions unless `allowPartial` is true.
|
|
876
906
|
*/
|
|
877
907
|
toBEEF (allowPartial?: boolean): number[] {
|
|
908
|
+
this.outputs.length
|
|
878
909
|
const writer = new Writer()
|
|
879
910
|
writer.writeUInt32LE(4022206465)
|
|
880
911
|
const BUMPs: MerklePath[] = []
|
|
@@ -919,10 +950,7 @@ export default class Transaction {
|
|
|
919
950
|
if (!hasProof) {
|
|
920
951
|
for (let i = 0; i < tx.inputs.length; i++) {
|
|
921
952
|
const input = tx.inputs[i]
|
|
922
|
-
if (typeof input.sourceTransaction === 'object')
|
|
923
|
-
addPathsAndInputs(input.sourceTransaction)
|
|
924
|
-
else if (!allowPartial)
|
|
925
|
-
throw new Error('A required source transaction is missing!')
|
|
953
|
+
if (typeof input.sourceTransaction === 'object') { addPathsAndInputs(input.sourceTransaction) } else if (!allowPartial) { throw new Error('A required source transaction is missing!') }
|
|
926
954
|
}
|
|
927
955
|
}
|
|
928
956
|
}
|