Closed EvanTedesco closed 4 months ago
This ended up being an issue with how I was generating the payload contents for the ExtrinsicPayload. I solved it by using the createType
interface to create the payload values E.G.
const latestBlockHash = (await api.rpc.chain.getFinalizedHead()).toHex();
const blockNumber = (await api.rpc.chain.getBlock(latestBlockHash)).block.header.number.toHex();
const era = api.registry.createType('ExtrinsicEra', {
current: blockNumber,
period: 64
});
const nonceRaw = await api.call.accountNonceApi.accountNonce(from);
const nonce = api.registry.createType('Compact<Index>', nonceRaw.toNumber());
const payload = {
address: from,
blockHash: latestBlockHash,
blockNumber,
era: era.toHex(),
genesisHash: api.genesisHash.toHex(),
method: tx.method.toHex(),
nonce: nonce.toHex(),
signedExtensions: api.registry.signedExtensions,
specVersion: api.runtimeVersion.specVersion.toHex(),
tip: api.registry.createType('Compact<Balance>', 0).toHex(),
transactionVersion: api.runtimeVersion.transactionVersion.toHex(),
version: tx.version
};
const extrinsicPayload = api.registry.createType('ExtrinsicPayload', payload, {
version: payload.version
});
const extrinsicPayloadU8a = extrinsicPayload.toU8a(true);
const actualPayload = extrinsicPayloadU8a.length > 256 ? api.registry.hash(extrinsicPayloadU8a) : extrinsicPayloadU8a;
const { recovery, signature } = await keyService.sign({ algorithm: 'ecdsa', encoding: 'raw', hash: keccakAsHex(actualPayload).substring(2), id: keyId });
const recoveryU8a = Uint8Array.from([recovery || 0]);
const sig = u8aToHex(u8aConcat(hexToU8a(signature), recoveryU8a));
const signedExtrinsic = tx.addSignature(from, sig, extrinsicPayload.toHex());
Then I was able to call .send successfully.
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue if you think you have a related problem or query.
Version:
Environment:
Language:
Hello, I would like to understand how to sign a balances.transferKeepAlive transaction completely offline with an existing signing service with ECDSA signatures. This is what I have so far that is working:
class SigningService { constructor() { this.keys = {} }
computePublicKeysFromPrivateKey({ key }) { return Promise.resolve({ publicKeys: [ { format: 'compressed', value: Buffer.from(secp256k1.getPublicKey(key, true)).toString('hex') }, { format: 'uncompressed', value: Buffer.from(secp256k1.getPublicKey(key, false)).toString('hex').substring(2) } ] }); }
async importPrivateKey({ key }) { const id = crypto.randomUUID(); const publicKeys = await this.computePublicKeysFromPrivateKey({ key });
}
async sign({ hash, id }) { const signature = secp256k1.sign(hash, keys[id]);
} }
const transferECDSA = async (to, from, amount) => { const keyService = new SigningService(); const { id: keyId } = await keyService.importPrivateKey({ key: '' });
const RPC_ENDPOINT = 'wss://rococo-muse-rpc.polkadot.io'; const wsProvider = new WsProvider(RPC_ENDPOINT); const api = await ApiPromise.create({ provider: wsProvider });
await api.isReady; await cryptoWaitReady();
const extrinsic = api.tx.balances.transferKeepAlive(to, amount);
const signer = { signPayload: async payload => { const extrinsicPayload = api.registry.createType('ExtrinsicPayload', payload, { mode: 0, registry: api.registry, version: EXTRINSIC_VERSION }); const extrinsicPayloadU8a = extrinsicPayload.toU8a({ method: true }); const actualPayload = extrinsicPayloadU8a.length > 256 ? api.registry.hash(extrinsicPayloadU8a) : extrinsicPayloadU8a; const { recovery, signature } = await keyService.sign({ hash: keccakAsHex(actualPayload).substring(2), id: keyId }); const recoveryU8a = Uint8Array.from([recovery || 0]);
};
const signedExtrinsic = await extrinsic.signAsync(from, { signer });
const unsub = await signedExtrinsic.send(({ status }) => { if (status.isInBlock) { console.log(
Transaction included at blockHash ${status.asInBlock}
); } else if (status.isFinalized) { console.log(Transaction finalized at blockHash ${status.asFinalized}
); unsub(); throw new Error('Exiting Process'); } }); }