Skip to content

Commit b5a985d

Browse files
committed
fix(sdk-coin-flrp): improve signature handling in Transaction class and update tests for ImportInCTxBuilder
1 parent a117680 commit b5a985d

File tree

3 files changed

+48
-42
lines changed

3 files changed

+48
-42
lines changed

modules/sdk-coin-flrp/src/lib/transaction.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,10 @@ export class Transaction extends BaseTransaction {
168168
);
169169
});
170170

171-
if (hasMatchingAddress) {
172-
const signature = await secp256k1.sign(unsignedBytes, prv);
171+
const signature = await secp256k1.sign(unsignedBytes, prv);
172+
let signatureSet = false;
173173

174-
let signatureSet = false;
174+
if (hasMatchingAddress) {
175175
// Use address-based slot matching (like AVAX-P)
176176
let checkSign: CheckSignature | undefined = undefined;
177177

@@ -196,11 +196,29 @@ export class Transaction extends BaseTransaction {
196196

197197
if (signatureSet) break;
198198
}
199+
}
199200

200-
if (!signatureSet) {
201-
throw new SigningError('No matching signature slot found for this private key');
201+
// Fallback: If address-based matching didn't work (e.g., ImportInC loaded from unsigned tx
202+
// where P-chain addresses aren't in addressMaps), try to sign the first empty slot.
203+
// This handles the case where we have empty credentials but signer address isn't in the map.
204+
if (!signatureSet) {
205+
for (const credential of unsignedTx.credentials) {
206+
const signatures = credential.getSignatures();
207+
for (let i = 0; i < signatures.length; i++) {
208+
if (isEmptySignature(signatures[i])) {
209+
credential.setSignature(i, signature);
210+
signatureSet = true;
211+
this._rawSignedBytes = undefined;
212+
break;
213+
}
214+
}
215+
if (signatureSet) break;
202216
}
203217
}
218+
219+
if (!signatureSet) {
220+
throw new SigningError('No matching signature slot found for this private key');
221+
}
204222
}
205223

206224
toBroadcastFormat(): string {

modules/sdk-coin-flrp/test/unit/lib/importInCTxBuilder.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,5 @@ describe('Flrp Import In C Tx Builder', () => {
4848
prv2: testData.privateKeys[1],
4949
},
5050
txHash: testData.txhash,
51-
// Skip signing from recovered unsigned: P-chain signer addresses aren't in the raw ImportInC transaction's address maps
52-
skipImportInCTxBuilder: true,
5351
});
5452
});

modules/sdk-coin-flrp/test/unit/lib/signFlowTestSuit.ts

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export interface signFlowTestSuitArgs {
99
fullSignedTxHex: string;
1010
privateKey: { prv1: string; prv2: string };
1111
txHash: string;
12-
skipImportInCTxBuilder?: boolean;
1312
}
1413

1514
/**
@@ -52,18 +51,14 @@ export default function signFlowTestSuit(data: signFlowTestSuitArgs): void {
5251
tx.id.should.equal(data.txHash);
5352
});
5453

55-
// TODO: Skip for ImportInC: P-chain signer addresses aren't in the raw transaction,
56-
// so signing a recovered unsigned tx requires addresses to be provided separately.
57-
if (!data.skipImportInCTxBuilder) {
58-
it('Should half sign tx from unsigned raw tx', async () => {
59-
const txBuilder = data.newTxFactory().from(data.unsignedTxHex);
60-
txBuilder.sign({ key: data.privateKey.prv1 });
61-
const tx = await txBuilder.build();
62-
const rawTx = tx.toBroadcastFormat();
63-
rawTx.should.equal(data.halfSignedTxHex);
64-
tx.id.should.equal(data.txHash);
65-
});
66-
}
54+
it('Should half sign tx from unsigned raw tx', async () => {
55+
const txBuilder = data.newTxFactory().from(data.unsignedTxHex);
56+
txBuilder.sign({ key: data.privateKey.prv1 });
57+
const tx = await txBuilder.build();
58+
const rawTx = tx.toBroadcastFormat();
59+
rawTx.should.equal(data.halfSignedTxHex);
60+
tx.id.should.equal(data.txHash);
61+
});
6762

6863
it('Should recover half signed tx from half signed raw tx', async () => {
6964
const txBuilder = data.newTxFactory().from(data.halfSignedTxHex);
@@ -92,28 +87,23 @@ export default function signFlowTestSuit(data: signFlowTestSuitArgs): void {
9287
tx.id.should.equal(data.txHash);
9388
});
9489

95-
if (!data.skipImportInCTxBuilder) {
96-
it('Should full sign a tx from half signed raw tx', async () => {
97-
const txBuilder = data.newTxFactory().from(data.halfSignedTxHex);
98-
txBuilder.sign({ key: data.privateKey.prv2 });
99-
const tx = await txBuilder.build();
100-
const rawTx = tx.toBroadcastFormat();
101-
rawTx.should.equal(data.fullSignedTxHex);
102-
tx.id.should.equal(data.txHash);
103-
});
104-
}
90+
it('Should full sign a tx from half signed raw tx', async () => {
91+
const txBuilder = data.newTxFactory().from(data.halfSignedTxHex);
92+
txBuilder.sign({ key: data.privateKey.prv2 });
93+
const tx = await txBuilder.build();
94+
const rawTx = tx.toBroadcastFormat();
95+
rawTx.should.equal(data.fullSignedTxHex);
96+
tx.id.should.equal(data.txHash);
97+
});
10598

106-
// Skip for ImportInC: P-chain signer addresses aren't in the recovered transaction's address maps
107-
if (!data.skipImportInCTxBuilder) {
108-
it('Should full sign a tx from unsigned raw tx', async () => {
109-
const txBuilder = data.newTxFactory().from(data.unsignedTxHex);
110-
txBuilder.sign({ key: data.privateKey.prv1 });
111-
txBuilder.sign({ key: data.privateKey.prv2 });
112-
const tx = await txBuilder.build();
113-
const rawTx = tx.toBroadcastFormat();
114-
rawTx.should.equal(data.fullSignedTxHex);
115-
tx.id.should.equal(data.txHash);
116-
});
117-
}
99+
it('Should full sign a tx from unsigned raw tx', async () => {
100+
const txBuilder = data.newTxFactory().from(data.unsignedTxHex);
101+
txBuilder.sign({ key: data.privateKey.prv1 });
102+
txBuilder.sign({ key: data.privateKey.prv2 });
103+
const tx = await txBuilder.build();
104+
const rawTx = tx.toBroadcastFormat();
105+
rawTx.should.equal(data.fullSignedTxHex);
106+
tx.id.should.equal(data.txHash);
107+
});
118108
});
119109
}

0 commit comments

Comments
 (0)