Skip to content

Commit c594d5f

Browse files
Copilotbhavidhingra
authored andcommitted
feat: add BIP322 broadcastable message processor script
TICKET: COIN-5426
1 parent 1979e1c commit c594d5f

File tree

3 files changed

+422
-0
lines changed

3 files changed

+422
-0
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# BIP322 Broadcastable Message Processor
2+
3+
This TypeScript script processes JSON files containing claims with BIP322 broadcastable messages. It demonstrates how to:
4+
5+
1. Parse JSON structure containing claims data
6+
2. Extract `broadcastableMessage` fields from claims
7+
3. Use `deserializeBIP322BroadcastableMessage()` to deserialize each message
8+
4. Use `generateBIP322MessageListAndVerifyFromMessageBroadcastable()` to verify and process messages
9+
10+
## Usage
11+
12+
```bash
13+
npx tsx process-bip322-claims.ts <json-file> <coin-name>
14+
```
15+
16+
### Arguments
17+
18+
- `json-file`: Path to JSON file containing claims data
19+
- `coin-name`: Coin name (supported: "btc", "tbtc4")
20+
21+
### Example
22+
23+
```bash
24+
npx tsx process-bip322-claims.ts sample-claims.json btc
25+
```
26+
27+
## Input JSON Format
28+
29+
The script expects JSON files with the following structure:
30+
31+
```json
32+
{
33+
"status": "success",
34+
"claims": [
35+
{
36+
"id": "claim-001",
37+
"broadcastableMessage": "7b227478486578223a22303130323033222c226d657373616765496e666f223a5b7b2261646472657373223a22736f6d6541646472657373222c226d657373616765223a22736f6d654d657373616765222c227075626b657973223a5b227075626b657931222c227075626b657932225d2c2273637269707454797065223a2270327368227d5d7d",
38+
...
39+
}
40+
],
41+
"count": 1,
42+
"pagination": {
43+
"limit": 100,
44+
"hasNext": false
45+
}
46+
}
47+
```
48+
49+
### Required Fields
50+
51+
- `claims`: Array of claim objects
52+
- `claims[].id`: Unique identifier for the claim
53+
- `claims[].broadcastableMessage`: Hex-encoded BIP322 broadcastable message (optional)
54+
55+
## Sample Data
56+
57+
The repository includes `sample-claims.json` with test data that demonstrates the script functionality. Note that the test data contains mock transaction hex values that will fail verification, but this is expected and demonstrates how the script handles both success and failure cases.
58+
59+
## Functions Used
60+
61+
### `deserializeBIP322BroadcastableMessage(hex: string)`
62+
63+
- **Purpose**: Deserializes a hex-encoded BIP322 broadcastable message
64+
- **Input**: Hex string containing serialized BIP322 message
65+
- **Output**: `BIP322MessageBroadcastable` object containing transaction hex and message info
66+
67+
### `generateBIP322MessageListAndVerifyFromMessageBroadcastable(messages: BIP322MessageBroadcastable[], coinName: string)`
68+
69+
- **Purpose**: Verifies BIP322 messages and extracts address/message pairs
70+
- **Input**: Array of deserialized BIP322 messages and coin name
71+
- **Output**: Array of `{address: string, message: string}` objects
72+
- **Supported Coins**: "btc", "tbtc4"
73+
74+
## Output
75+
76+
The script provides detailed output showing:
77+
78+
1. JSON file parsing results
79+
2. Claims processing summary
80+
3. Message extraction details
81+
4. Deserialization results for each message
82+
5. Verification results or detailed error information
83+
6. Final summary with counts and extracted message information
84+
85+
## Error Handling
86+
87+
The script handles various error conditions:
88+
89+
- **File not found**: Clear error message with file path
90+
- **Invalid JSON**: Parse error indication
91+
- **Deserialization failures**: Per-message error reporting
92+
- **Verification failures**: Detailed error messages with fallback to show extracted data
93+
- **Unsupported coin names**: Clear error about supported values
94+
95+
## Development Notes
96+
97+
This script is designed as a usage example for BitGo's BIP322 utilities. In production environments:
98+
99+
1. Use actual BIP322 transaction data with valid Bitcoin transaction hex
100+
2. Ensure broadcastable messages are properly formatted
101+
3. Handle network-specific addresses and script types appropriately
102+
4. Implement proper error recovery and logging
103+
104+
## Example Output
105+
106+
```
107+
Processing BIP322 claims from: sample-claims.json
108+
Coin: btc
109+
110+
Status: success
111+
Total claims: 3
112+
113+
Extracting broadcastable messages...
114+
Claim 1 (ID: claim-001): Found broadcastable message
115+
Claim 2 (ID: claim-002): Found broadcastable message
116+
Claim 3 (ID: claim-003): No broadcastable message found
117+
118+
Found 2 broadcastable message(s)
119+
120+
Deserializing BIP322 broadcastable messages...
121+
Deserializing message 1...
122+
✓ Successfully deserialized message 1
123+
Transaction hex length: 6
124+
Message info count: 1
125+
Message 1: Address: someAddress, Script type: p2sh
126+
127+
=== SUMMARY ===
128+
Total claims processed: 3
129+
Broadcastable messages found: 2
130+
Successfully deserialized: 2
131+
Message info extracted: 2
132+
```
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/**
2+
* Process BIP322 broadcastable messages from JSON claims data
3+
*
4+
* This script reads a JSON file containing an array of claims, each with a
5+
* broadcastableMessage string. It extracts and deserializes the messages,
6+
* then verifies them using BitGo's BIP322 utilities.
7+
*
8+
* Usage:
9+
* npx tsx process-bip322-claims.ts [path-to-json-file] [coin-name]
10+
*
11+
* Example:
12+
* npx tsx process-bip322-claims.ts sample-claims.json btc
13+
*
14+
* Copyright 2025, BitGo, Inc. All Rights Reserved.
15+
*/
16+
17+
import * as fs from 'fs';
18+
import * as path from 'path';
19+
import {
20+
BIP322MessageBroadcastable,
21+
deserializeBIP322BroadcastableMessage,
22+
generateBIP322MessageListAndVerifyFromMessageBroadcastable
23+
} from "@bitgo/abstract-utxo/dist/src/transaction/bip322";
24+
25+
interface ClaimData {
26+
id: string;
27+
broadcastableMessage: string;
28+
[key: string]: any;
29+
}
30+
31+
interface ClaimsResponse {
32+
status: string;
33+
claims: ClaimData[];
34+
count: number;
35+
pagination?: {
36+
limit: number;
37+
hasNext: boolean;
38+
};
39+
}
40+
41+
/**
42+
* Process BIP322 broadcastable messages from a JSON file
43+
* @param filePath - Path to the JSON file containing claims data
44+
* @param coinName - The coin name (e.g., 'btc', 'tbtc4')
45+
*/
46+
async function processBIP322Claims(filePath: string, coinName: string): Promise<void> {
47+
try {
48+
console.log(`Processing BIP322 claims from: ${filePath}`);
49+
console.log(`Coin: ${coinName}`);
50+
console.log('');
51+
52+
// Read and parse the JSON file
53+
const jsonData = fs.readFileSync(filePath, 'utf8');
54+
const claimsData: ClaimsResponse = JSON.parse(jsonData);
55+
56+
console.log(`Status: ${claimsData.status}`);
57+
console.log(`Total claims: ${claimsData.count}`);
58+
console.log('');
59+
60+
if (!claimsData.claims || claimsData.claims.length === 0) {
61+
console.log('No claims found in the data.');
62+
return;
63+
}
64+
65+
// Extract broadcastable messages from claims
66+
const broadcastableMessages: string[] = [];
67+
68+
console.log('Extracting broadcastable messages...');
69+
claimsData.claims.forEach((claim, index) => {
70+
if (claim.broadcastableMessage) {
71+
broadcastableMessages.push(claim.broadcastableMessage);
72+
console.log(` Claim ${index + 1} (ID: ${claim.id}): Found broadcastable message`);
73+
} else {
74+
console.log(` Claim ${index + 1} (ID: ${claim.id}): No broadcastable message found`);
75+
}
76+
});
77+
78+
if (broadcastableMessages.length === 0) {
79+
console.log('No broadcastable messages found in any claims.');
80+
return;
81+
}
82+
83+
console.log(`\nFound ${broadcastableMessages.length} broadcastable message(s)`);
84+
console.log('');
85+
86+
// Deserialize each broadcastable message
87+
console.log('Deserializing BIP322 broadcastable messages...');
88+
const deserializedMessages: BIP322MessageBroadcastable[] = [];
89+
90+
for (let i = 0; i < broadcastableMessages.length; i++) {
91+
try {
92+
const message = broadcastableMessages[i];
93+
console.log(` Deserializing message ${i + 1}...`);
94+
95+
const deserializedMessage = deserializeBIP322BroadcastableMessage(message);
96+
deserializedMessages.push(deserializedMessage);
97+
98+
console.log(` ✓ Successfully deserialized message ${i + 1}`);
99+
console.log(` Transaction hex length: ${deserializedMessage.txHex.length}`);
100+
console.log(` Message info count: ${deserializedMessage.messageInfo.length}`);
101+
102+
// Log details of each message info
103+
deserializedMessage.messageInfo.forEach((info, infoIndex) => {
104+
console.log(` Message ${infoIndex + 1}: Address: ${info.address}, Script type: ${info.scriptType}`);
105+
});
106+
} catch (error) {
107+
console.error(` ✗ Failed to deserialize message ${i + 1}:`, error.message);
108+
}
109+
}
110+
111+
if (deserializedMessages.length === 0) {
112+
console.log('No messages were successfully deserialized.');
113+
return;
114+
}
115+
116+
console.log(`\nSuccessfully deserialized ${deserializedMessages.length} message(s)`);
117+
console.log('');
118+
119+
// Process deserialized messages with BIP322 verification
120+
console.log('Verifying and generating message list...');
121+
console.log('Note: Verification may fail with test data if transaction hex is not valid Bitcoin data');
122+
console.log('');
123+
124+
try {
125+
const verifiedResults = generateBIP322MessageListAndVerifyFromMessageBroadcastable(
126+
deserializedMessages,
127+
coinName
128+
);
129+
130+
console.log(`✓ Successfully verified and processed all messages`);
131+
console.log(`\nResults (${verifiedResults.length} verified message(s)):`);
132+
console.log('');
133+
134+
verifiedResults.forEach((result, index) => {
135+
console.log(`${index + 1}. Address: ${result.address}`);
136+
console.log(` Message: ${result.message}`);
137+
console.log('');
138+
});
139+
140+
// Summary
141+
console.log('=== SUMMARY ===');
142+
console.log(`Total claims processed: ${claimsData.claims.length}`);
143+
console.log(`Broadcastable messages found: ${broadcastableMessages.length}`);
144+
console.log(`Successfully deserialized: ${deserializedMessages.length}`);
145+
console.log(`Successfully verified: ${verifiedResults.length}`);
146+
} catch (error) {
147+
console.error('✗ Failed to verify messages:', error.message);
148+
console.log('');
149+
console.log('This is expected when using test data with invalid transaction hex.');
150+
console.log('In production, you would use actual BIP322 transaction data.');
151+
console.log('');
152+
153+
// Show what would have been extracted from the messages
154+
console.log('However, we can still show the extracted message information:');
155+
console.log('');
156+
157+
interface ExtractedInfo {
158+
address: string;
159+
message: string;
160+
scriptType: string;
161+
pubkeys: string[];
162+
source: string;
163+
}
164+
165+
const extractedInfo: ExtractedInfo[] = [];
166+
deserializedMessages.forEach((message, msgIndex) => {
167+
message.messageInfo.forEach((info, infoIndex) => {
168+
extractedInfo.push({
169+
address: info.address,
170+
message: info.message,
171+
scriptType: info.scriptType,
172+
pubkeys: info.pubkeys,
173+
source: `Message ${msgIndex + 1}, Info ${infoIndex + 1}`,
174+
});
175+
});
176+
});
177+
178+
extractedInfo.forEach((info, index) => {
179+
console.log(`${index + 1}. Address: ${info.address}`);
180+
console.log(` Message: ${info.message}`);
181+
console.log(` Script Type: ${info.scriptType}`);
182+
console.log(` Public Keys: ${info.pubkeys.join(', ')}`);
183+
console.log(` Source: ${info.source}`);
184+
console.log('');
185+
});
186+
187+
// Summary
188+
console.log('=== SUMMARY ===');
189+
console.log(`Total claims processed: ${claimsData.claims.length}`);
190+
console.log(`Broadcastable messages found: ${broadcastableMessages.length}`);
191+
console.log(`Successfully deserialized: ${deserializedMessages.length}`);
192+
console.log(`Verification failed (expected with test data): ${deserializedMessages.length}`);
193+
console.log(`Message info extracted: ${extractedInfo.length}`);
194+
}
195+
} catch (error) {
196+
console.error('Error processing BIP322 claims:', error.message);
197+
198+
if (error.code === 'ENOENT') {
199+
console.error(`File not found: ${filePath}`);
200+
} else if (error instanceof SyntaxError) {
201+
console.error('Invalid JSON format in file');
202+
}
203+
204+
process.exit(1);
205+
}
206+
}
207+
208+
/**
209+
* Main function to handle command line arguments and run the processor
210+
*/
211+
async function main(): Promise<void> {
212+
const args = process.argv.slice(2);
213+
214+
if (args.length !== 2) {
215+
console.log('Usage: npx tsx process-bip322-claims.ts <json-file> <coin-name>');
216+
console.log('');
217+
console.log('Arguments:');
218+
console.log(' json-file Path to JSON file containing claims data');
219+
console.log(' coin-name Coin name (e.g., "btc", "tbtc4")');
220+
console.log('');
221+
console.log('Example:');
222+
console.log(' npx tsx process-bip322-claims.ts sample-claims.json btc');
223+
process.exit(1);
224+
}
225+
226+
const [filePath, coinName] = args;
227+
228+
// Resolve file path relative to current working directory
229+
const resolvedPath = path.resolve(filePath);
230+
231+
await processBIP322Claims(resolvedPath, coinName);
232+
}
233+
234+
// Run the main function
235+
main().catch((error) => {
236+
console.error('Unexpected error:', error);
237+
process.exit(1);
238+
});

0 commit comments

Comments
 (0)