Skip to content

Commit

Permalink
feat: Add silent block encoding and decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
seekersoftec committed Sep 11, 2024
1 parent ce6b476 commit bb61c6b
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 49 deletions.
17 changes: 1 addition & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@nestjs/platform-express": "^10.3.7",
"@nestjs/platform-socket.io": "^10.3.7",
"@nestjs/schedule": "^3.0.0",
"@nestjs/swagger": "^7.4.0",
"@nestjs/swagger": "^7.3.1",
"@nestjs/typeorm": "^10.0.2",
"@nestjs/websockets": "^10.3.7",
"axios": "^1.7.2",
Expand All @@ -43,7 +43,6 @@
"rxjs": "^7.2.0",
"secp256k1": "^5.0.0",
"sqlite3": "^5.1.7",
"swagger-ui-express": "^5.0.1",
"typeorm": "^0.3.20"
},
"devDependencies": {
Expand Down
7 changes: 4 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { AppModule } from '@/app.module';
import { ConfigService } from '@nestjs/config';
import { NestExpressApplication } from '@nestjs/platform-express';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import * as packageJson from 'package.json';

declare const module: any;

Expand All @@ -16,8 +15,10 @@ async function bootstrap() {
// Swagger Options
const options = new DocumentBuilder()
.setTitle('Silent Payment Indexer API')
.setDescription(packageJson.description)
.setVersion(packageJson.version)
.setDescription(
'An indexer for serving bitcoin silent payment indexes!!',
)
.setVersion('0.0.1')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup(`/docs`, app, document);
Expand Down
95 changes: 67 additions & 28 deletions src/silent-blocks/silent-blocks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ export class SilentBlocksService {
*/
private encodeSilentBlock(transactions: Transaction[]): Buffer {
const silentBlockType = SILENT_PAYMENT_BLOCK_TYPE;
const transactionBuffers: Buffer[] = [];

transactions.forEach((tx) => {
const transactionBuffers = transactions.map((tx) => {
const txidBuffer = Buffer.from(tx.id, 'hex');

const outputsCount = tx.outputs.length;
Expand All @@ -42,14 +41,12 @@ export class SilentBlocksService {

const scanTweakBuffer = Buffer.from(tx.scanTweak, 'hex');

const transactionBuffer = Buffer.concat([
return Buffer.concat([
txidBuffer,
outputsCountBuffer,
outputsBuffer,
scanTweakBuffer,
]);

transactionBuffers.push(transactionBuffer);
});

const transactionsCountBuffer = this.encodeVarInt(
Expand Down Expand Up @@ -78,41 +75,50 @@ export class SilentBlocksService {
const silentBlockType = buffer.readUInt8(offset);
offset += 1;

// Read the number of transactions
const transactionsCount = this.decodeVarInt(buffer, offset);
offset += 1;
if (silentBlockType !== SILENT_PAYMENT_BLOCK_TYPE) {
throw new Error('Invalid silent block type');
}

// Read the number of transactions (Bitcoin varInt format)
const { value: transactionsCount, bytesRead: txCountBytesRead } =
this.decodeVarInt(buffer, offset);
offset += txCountBytesRead;

// Process each transaction
for (let i = 0; i < transactionsCount; i++) {
// Read the txid
for (let i = 0; i < Number(transactionsCount); i++) {
// Read the txid (32 bytes)
const txid = buffer.subarray(offset, offset + 32).toString('hex');
offset += 32;

// Read the number of outputs
const outputsCount = this.decodeVarInt(buffer, offset);
offset += 1;
// Read the number of outputs (Bitcoin varInt format)
const { value: outputsCount, bytesRead: outCountBytesRead } =
this.decodeVarInt(buffer, offset);
offset += outCountBytesRead;

// Read the outputs
const outputs = [];
for (let j = 0; j < outputsCount; j++) {
const value = buffer.readBigUInt64BE(offset); // 8-byte value
for (let j = 0; j < Number(outputsCount); j++) {
// Read the value (8-byte big-endian)
const value = buffer.readBigUInt64BE(offset);
offset += 8;

// Read the pubKey (32-byte pubkey)
const pubKey = buffer
.subarray(offset, offset + 32)
.toString('hex'); // 32-byte pubkey
.toString('hex');
offset += 32;

const vout = buffer.readUInt32BE(offset); // 4-byte vout
// Read the vout (4-byte big-endian)
const vout = buffer.readUInt32BE(offset);
offset += 4;

outputs.push({ value: Number(value), pubKey, vout });
}

// Read the scanTweak
// Read the scanTweak (33 bytes)
const scanTweak = buffer
.subarray(offset, offset + 33)
.toString('hex'); // 33-byte scan tweak
.toString('hex');
offset += 33;

// Create the transaction object
Expand All @@ -121,7 +127,7 @@ export class SilentBlocksService {
outputs,
scanTweak,

// These are not included in the block design, so just use dummy values
// These fields are not in the block, so use dummy values
blockHeight: 0,
blockHash: '',
isSpent: false,
Expand All @@ -134,24 +140,57 @@ export class SilentBlocksService {
}

/**
* Encodes a variable integer (varInt) into a Buffer.
* Encodes a variable integer (varInt) into a Buffer following Bitcoin's encoding scheme.
* @param value - The integer value to encode.
* @returns A Buffer containing the encoded varInt.
*/
private encodeVarInt(value: number): Buffer {
const buffer = Buffer.alloc(1);
buffer.writeUInt8(value);
return buffer;
if (value < 0xfd) {
return Buffer.from([value]);
} else if (value <= 0xffff) {
const buffer = Buffer.alloc(3);
buffer.writeUInt8(0xfd, 0);
buffer.writeUInt16LE(value, 1);
return buffer;
} else if (value <= 0xffffffff) {
const buffer = Buffer.alloc(5);
buffer.writeUInt8(0xfe, 0);
buffer.writeUInt32LE(value, 1);
return buffer;
} else {
const buffer = Buffer.alloc(9);
buffer.writeUInt8(0xff, 0);
buffer.writeBigUInt64LE(BigInt(value), 1);
return buffer;
}
}

/**
* Decodes a variable integer (varInt) from a Buffer.
* Decodes a variable integer (varInt) from a Buffer following Bitcoin's encoding scheme.
* @param buffer - The buffer containing the encoded varInt.
* @param offset - The offset in the buffer where the varInt starts.
* @returns The decoded integer value.
* @returns The decoded integer value and the number of bytes read.
*/
private decodeVarInt(buffer: Buffer, offset: number): number {
return buffer.readUInt8(offset);
private decodeVarInt(
buffer: Buffer,
offset: number,
): { value: number | bigint; bytesRead: number } {
const firstByte = buffer.readUInt8(offset);

// Check for invalid prefix bytes
if (firstByte < 0x00 || firstByte > 0xff) {
throw new Error('Invalid VarInt prefix byte');
}

if (firstByte < 0xfd) {
return { value: firstByte, bytesRead: 1 };
} else if (firstByte === 0xfd) {
return { value: buffer.readUInt16LE(offset + 1), bytesRead: 3 };
} else if (firstByte === 0xfe) {
return { value: buffer.readUInt32LE(offset + 1), bytesRead: 5 };
} else {
return { value: buffer.readBigUInt64LE(offset + 1), bytesRead: 9 };
}
}

/**
Expand Down

0 comments on commit bb61c6b

Please sign in to comment.