diff --git a/packages/core/src/encoding.ts b/packages/core/src/encoding.ts index fba6105..9d50156 100644 --- a/packages/core/src/encoding.ts +++ b/packages/core/src/encoding.ts @@ -3,7 +3,13 @@ import secp256k1 from 'secp256k1'; import { Buffer } from 'buffer'; import { Network } from 'bitcoinjs-lib'; import { bitcoin } from 'bitcoinjs-lib/src/networks'; -import { createTaggedHash, serialiseUint32 } from './utility'; +import { + createTaggedHash, + encodingLength, + readVarInt, + serialiseUint32, +} from './utility'; +import { SilentBlock } from './interface'; export const encodeSilentPaymentAddress = ( scanPubKey: Uint8Array, @@ -63,3 +69,40 @@ export const createLabeledSilentPaymentAddress = ( const hrpFromNetwork = (network: Network): string => { return network.bech32 === 'bc' ? 'sp' : 'tsp'; }; + +export const parseSilentBlock = (data: Buffer): SilentBlock => { + const type = data.readUInt8(0); + const transactions = []; + let cursor = 1; + const count = readVarInt(data, cursor); + cursor += encodingLength(count); + + for (let i = 0; i < count; i++) { + const txid = data.subarray(cursor, cursor + 32).toString('hex'); + cursor += 32; + + const outputs = []; + const outputCount = readVarInt(data, cursor); + cursor += encodingLength(outputCount); + + for (let j = 0; j < outputCount; j++) { + const value = Number(data.readBigUInt64BE(cursor)); + cursor += 8; + + const pubKey = data.subarray(cursor, cursor + 32).toString('hex'); + cursor += 32; + + const vout = data.readUint32BE(cursor); + cursor += 4; + + outputs.push({ value, pubKey, vout }); + } + + const scanTweak = data.subarray(cursor, cursor + 33).toString('hex'); + cursor += 33; + + transactions.push({ txid, outputs, scanTweak }); + } + + return { type, transactions }; +}; diff --git a/packages/core/src/interface.ts b/packages/core/src/interface.ts index 7bbd7f1..28d1d00 100644 --- a/packages/core/src/interface.ts +++ b/packages/core/src/interface.ts @@ -29,3 +29,16 @@ export type Input = { sequence: number; witness: Buffer[]; }; + +export type SilentBlock = { + type: number; + transactions: { + txid: string; + outputs: { + value: number; + pubKey: string; + vout: number; + }[]; + scanTweak: string; + }[]; +}; diff --git a/packages/core/test/encoding.spec.ts b/packages/core/test/encoding.spec.ts index dc95b6c..fca71c1 100644 --- a/packages/core/test/encoding.spec.ts +++ b/packages/core/test/encoding.spec.ts @@ -2,9 +2,10 @@ import { createLabeledSilentPaymentAddress, decodeSilentPaymentAddress, encodeSilentPaymentAddress, + parseSilentBlock, } from '../src'; import { Buffer } from 'buffer'; -import { unlabelled, labelled } from './fixtures/encoding'; +import { unlabelled, labelled, silentBlock } from './fixtures/encoding'; describe('Encoding', () => { describe.each(unlabelled)('Encode/Decode SP', (data) => { @@ -37,4 +38,11 @@ describe('Encoding', () => { ).toBe(address); }, ); + + it.each(silentBlock)('should parse silent block', (blockData) => { + const parsedBlock = parseSilentBlock( + Buffer.from(blockData.encodedBlockHex, 'hex'), + ); + expect(parsedBlock).toStrictEqual(blockData.parsedBlock); + }); }); diff --git a/packages/core/test/fixtures/encoding.ts b/packages/core/test/fixtures/encoding.ts index 3cf33bf..220ccbd 100644 --- a/packages/core/test/fixtures/encoding.ts +++ b/packages/core/test/fixtures/encoding.ts @@ -72,3 +72,51 @@ export const labelled = [ 'sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5', }, ]; + +export const silentBlock = [ + { + parsedBlock: { + type: 0, + transactions: [ + { + txid: 'd60cdcc1bdb78d44bf83e2ae0efa94bbb1187ba2397e4a40960d853b78e15b4f', + scanTweak: + '0381a4ef11a26108cf10d8c33305b77aec009beed82a77bb47b716a04ae9ae5bf1', + outputs: [ + { + pubKey: 'ca80a975d09b84b385e39d779293121b9a7c80942d824d2496f098e3f077ced3', + value: 24771038, + vout: 1, + }, + ], + }, + { + txid: 'cf655a07ed9b672ecf811ce8b3b69257eecaf0518edd025d7014d7c0177050cb', + scanTweak: + '03d87550a4ba8ed7dd37df7739a021d6d212e70e8a8e6e9e70249c13a11fd9f412', + outputs: [ + { + pubKey: '5af9436eb515871413a913f24290ba003eab7a64b9c62cb72051599a383de8ff', + value: 26940, + vout: 0, + }, + { + pubKey: 'e7a3ab33572f47b074878c749a5f9a5d7e026f10b6ffcab1388e9356c07828fa', + value: 4400, + vout: 1, + }, + ], + }, + ], + }, + encodedBlockHex: + '0002d60cdcc1bdb78d44bf83e2ae0efa94bbb1187ba2397e4a40960d853b78e15b4f01000000000179f9deca80a975d09b84b385e39d779293121b9a7c80942d824d2496f098e3f077ced3000000010381a4ef11a26108cf10d8c33305b77aec009beed82a77bb47b716a04ae9ae5bf1cf655a07ed9b672ecf811ce8b3b69257eecaf0518edd025d7014d7c0177050cb02000000000000693c5af9436eb515871413a913f24290ba003eab7a64b9c62cb72051599a383de8ff000000000000000000001130e7a3ab33572f47b074878c749a5f9a5d7e026f10b6ffcab1388e9356c07828fa0000000103d87550a4ba8ed7dd37df7739a021d6d212e70e8a8e6e9e70249c13a11fd9f412', + }, + { + parsedBlock: { + type: 0, + transactions: [], + }, + encodedBlockHex: '0000', + }, +];