Skip to content

Commit d22b297

Browse files
authored
Merge pull request #8830 from BitGo/CSHLD-886-fix-hashbytes-padding
CSHLD-886: Fix incorrect hashbytes padding in sBTC withdraw builder
2 parents b2239f5 + e8e09b3 commit d22b297

2 files changed

Lines changed: 55 additions & 12 deletions

File tree

modules/sdk-coin-stx/src/lib/sbtcWithdrawBuilder.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import { decodeBtcAddress, isValidBtcAddress } from './btcAddressUtils';
2525

2626
const SBTC_TOKEN_CONTRACT_NAME = 'sbtc-token';
2727
const SBTC_TOKEN_ASSET_NAME = 'sbtc-token';
28-
const HASHBYTES_BUFFER_LENGTH = 32;
2928

3029
export class SbtcWithdrawBuilder extends AbstractContractBuilder {
3130
private _withdrawParams: SbtcWithdrawParams | undefined;
@@ -130,20 +129,11 @@ export class SbtcWithdrawBuilder extends AbstractContractBuilder {
130129

131130
private withdrawParamsToFunctionArgs(params: SbtcWithdrawParams) {
132131
const decoded = decodeBtcAddress(params.btcAddress);
133-
134-
// Pad 20-byte hashes to 32 bytes with trailing zeros per sBTC contract spec (buff 32)
135-
let hashBytes = decoded.hashBytes;
136-
if (hashBytes.length < HASHBYTES_BUFFER_LENGTH) {
137-
const padded = Buffer.alloc(HASHBYTES_BUFFER_LENGTH, 0);
138-
hashBytes.copy(padded);
139-
hashBytes = padded;
140-
}
141-
142132
return [
143133
uintCV(params.amount),
144134
tupleCV({
145135
version: bufferCV(Buffer.from([decoded.version])),
146-
hashbytes: bufferCV(hashBytes),
136+
hashbytes: bufferCV(decoded.hashBytes),
147137
}),
148138
uintCV(params.maxFee),
149139
];

modules/sdk-coin-stx/test/unit/transactionBuilder/sbtcWithdrawBuilder.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import should from 'should';
2-
import { pubKeyfromPrivKey, publicKeyToString } from '@stacks/transactions';
2+
import { ClarityType, pubKeyfromPrivKey, publicKeyToString } from '@stacks/transactions';
3+
import { ContractCallPayload } from '@stacks/transactions/dist/payload';
34

45
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
56
import { BitGoAPI } from '@bitgo/sdk-api';
@@ -52,6 +53,18 @@ describe('Stacks: sBTC Withdraw Builder', function () {
5253
should.exist(txJson.payload);
5354
txJson.payload.should.have.property('contractName', 'sbtc-withdrawal');
5455
txJson.payload.should.have.property('functionName', 'initiate-withdrawal-request');
56+
57+
// Verify hashbytes is 20 bytes for P2PKH
58+
const payload = (tx as StxLib.Transaction).stxTransaction.payload as ContractCallPayload;
59+
const recipientTuple = payload.functionArgs[1];
60+
should.equal(recipientTuple.type, ClarityType.Tuple);
61+
if (recipientTuple.type === ClarityType.Tuple) {
62+
const hashbytes = recipientTuple.data['hashbytes'];
63+
should.equal(hashbytes.type, ClarityType.Buffer);
64+
if (hashbytes.type === ClarityType.Buffer) {
65+
hashbytes.buffer.length.should.equal(20);
66+
}
67+
}
5568
});
5669

5770
it('a withdrawal with P2SH address', async () => {
@@ -68,6 +81,16 @@ describe('Stacks: sBTC Withdraw Builder', function () {
6881
const tx = await builder.build();
6982
const txJson = tx.toJson();
7083
txJson.payload.should.have.property('functionName', 'initiate-withdrawal-request');
84+
85+
// Verify hashbytes is 20 bytes for P2SH
86+
const payload = (tx as StxLib.Transaction).stxTransaction.payload as ContractCallPayload;
87+
const recipientTuple = payload.functionArgs[1];
88+
if (recipientTuple.type === ClarityType.Tuple) {
89+
const hashbytes = recipientTuple.data['hashbytes'];
90+
if (hashbytes.type === ClarityType.Buffer) {
91+
hashbytes.buffer.length.should.equal(20);
92+
}
93+
}
7194
});
7295

7396
it('a withdrawal with P2WPKH (bech32) address', async () => {
@@ -84,6 +107,16 @@ describe('Stacks: sBTC Withdraw Builder', function () {
84107
const tx = await builder.build();
85108
const txJson = tx.toJson();
86109
txJson.payload.should.have.property('functionName', 'initiate-withdrawal-request');
110+
111+
// Verify hashbytes is 20 bytes for P2WPKH
112+
const payload = (tx as StxLib.Transaction).stxTransaction.payload as ContractCallPayload;
113+
const recipientTuple = payload.functionArgs[1];
114+
if (recipientTuple.type === ClarityType.Tuple) {
115+
const hashbytes = recipientTuple.data['hashbytes'];
116+
if (hashbytes.type === ClarityType.Buffer) {
117+
hashbytes.buffer.length.should.equal(20);
118+
}
119+
}
87120
});
88121

89122
it('a withdrawal with P2WSH (bech32) address', async () => {
@@ -100,6 +133,16 @@ describe('Stacks: sBTC Withdraw Builder', function () {
100133
const tx = await builder.build();
101134
const txJson = tx.toJson();
102135
txJson.payload.should.have.property('functionName', 'initiate-withdrawal-request');
136+
137+
// Verify hashbytes is 32 bytes for P2WSH
138+
const payload = (tx as StxLib.Transaction).stxTransaction.payload as ContractCallPayload;
139+
const recipientTuple = payload.functionArgs[1];
140+
if (recipientTuple.type === ClarityType.Tuple) {
141+
const hashbytes = recipientTuple.data['hashbytes'];
142+
if (hashbytes.type === ClarityType.Buffer) {
143+
hashbytes.buffer.length.should.equal(32);
144+
}
145+
}
103146
});
104147

105148
it('a withdrawal with P2TR (bech32m) address', async () => {
@@ -116,6 +159,16 @@ describe('Stacks: sBTC Withdraw Builder', function () {
116159
const tx = await builder.build();
117160
const txJson = tx.toJson();
118161
txJson.payload.should.have.property('functionName', 'initiate-withdrawal-request');
162+
163+
// Verify hashbytes is 32 bytes for P2TR
164+
const payload = (tx as StxLib.Transaction).stxTransaction.payload as ContractCallPayload;
165+
const recipientTuple = payload.functionArgs[1];
166+
if (recipientTuple.type === ClarityType.Tuple) {
167+
const hashbytes = recipientTuple.data['hashbytes'];
168+
if (hashbytes.type === ClarityType.Buffer) {
169+
hashbytes.buffer.length.should.equal(32);
170+
}
171+
}
119172
});
120173
});
121174

0 commit comments

Comments
 (0)