diff --git a/modules/sdk-coin-polyx/src/polyx.ts b/modules/sdk-coin-polyx/src/polyx.ts index ff1d0c1765..a72bcad729 100644 --- a/modules/sdk-coin-polyx/src/polyx.ts +++ b/modules/sdk-coin-polyx/src/polyx.ts @@ -108,7 +108,7 @@ export class Polyx extends SubstrateCoin { protected async getFee(destAddr: string, srcAddr: string, amount: number): Promise { const api = await this.getInitializedNodeAPI(); - const info = await api.tx.balances.transfer(destAddr, amount).paymentInfo(srcAddr); + const info = await api.tx.balances.transferAllowDeath(destAddr, amount).paymentInfo(srcAddr); return info.partialFee.toNumber(); } @@ -195,7 +195,6 @@ export class Polyx extends SubstrateCoin { assert(params.walletPassphrase, 'missing wallet passphrase'); const signingMaterial = await this.getEddsaSigningMaterial(params.userKey, params.walletPassphrase); - const ED25519_PREFIX = 0x00; const substrateKeyPair = new SubstrateKeyPair({ pub: accountId }); if (signingMaterial.version === 'v2') { const rawSig = await this.signSubstrateMpcV2Recovery({ @@ -207,10 +206,7 @@ export class Polyx extends SubstrateCoin { derivationPath: currPath, bitgo: this.bitgo, }); - txBuilder.addSignature( - { pub: substrateKeyPair.getKeys().pub }, - Buffer.concat([Buffer.from([ED25519_PREFIX]), rawSig]) - ); + txBuilder.addSignature({ pub: substrateKeyPair.getKeys().pub }, rawSig); } else { const userSigningMaterial = JSON.parse(signingMaterial.userPrv) as EDDSAMethodTypes.UserSigningMaterial; const backupPrv = await this.bitgo.decrypt({ diff --git a/modules/sdk-coin-polyx/test/unit/polyx.ts b/modules/sdk-coin-polyx/test/unit/polyx.ts index 284c0db38b..3460abc6cb 100644 --- a/modules/sdk-coin-polyx/test/unit/polyx.ts +++ b/modules/sdk-coin-polyx/test/unit/polyx.ts @@ -234,11 +234,11 @@ describe('Polyx:', function () { (result.serializedTx as string).should.be.a.String().and.not.be.empty(); sandBox.assert.notCalled(getTSSSignatureSpy); - // Substrate MultiSignature Ed25519 discriminant (0x00) must prefix the 64-byte signature. sandBox.assert.calledOnce(addSignatureSpy); const signature: Buffer = addSignatureSpy.firstCall.args[1]; - signature.length.should.equal(65); - signature[0].should.equal(0x00); + // constructSignedPayload prepends the 0x00 Ed25519 discriminant internally; + // recover() must pass the raw 64-byte signature unchanged. + signature.length.should.equal(64); }); it('should produce a cryptographically valid Ed25519 signature', async function () { @@ -331,6 +331,26 @@ describe('Polyx:', function () { const paramsWithoutPassphrase = { ...mpcV2RecoverParams, walletPassphrase: undefined }; await baseCoin.recover(paramsWithoutPassphrase).should.be.rejectedWith('missing wallet passphrase'); }); + + it('should pass the raw 64-byte signature to addSignature on MPCv2 path', async function () { + // Regression: previously wrapped rawSig with a manual Ed25519 discriminant (0x00) + // before addSignature. constructSignedPayload already prepends that discriminant, + // so wrapping here caused a double prefix that shifted the on-wire signature by + // one byte, dropping the last byte of sigma and producing + // `1010: Bad signature` on-chain. + const rawSig = Buffer.alloc(64, 0xab); + sandBox + .stub(baseCoin as unknown as { signSubstrateMpcV2Recovery: unknown }, 'signSubstrateMpcV2Recovery') + .resolves(rawSig); + const addSignatureSpy = sandBox.spy(TransferBuilder.prototype, 'addSignature'); + + await baseCoin.recover(mpcV2RecoverParams); + + sandBox.assert.calledOnce(addSignatureSpy); + const sig: Buffer = addSignatureSpy.firstCall.args[1]; + sig.length.should.equal(64); + sig.should.deepEqual(rawSig); + }); }); });