Tx signing fails with "rlp: input string too long for uint32" on ShardID field when using signed RLP
When trying to send a signed transaction on Harmony ONE, I receive the following error:
rlp: input string too long for uint32, decoding into (types.Transaction)(types.txdata).ShardID
This happens when broadcasting a manually signed transaction. Here's the signed RLP:
0xf86e80856edf2a079e82520894fee17838aaa53fd42603c27b4e5a921edda4980e862d79883d20008084c6afa5e4a0581dfd645192f3196e3a7e0f4fe7ea6aa56b8fea6ca2f4367b55425305b640aaa0eedb414bac89e5d78ffe9db69d7095850d39d809abfe1d24aaa03457bc30ce10
I’m not using web3’s sendTransaction — I’m manually signing and broadcasting.
After decoding the transaction, I suspect the shardID or toShardID is being misinterpreted or encoded improperly (perhaps as a BigInt or larger-than-expected value). The fields are supposed to be uint32, but decoding fails.
Please confirm:
Is this an RLP schema mismatch?
Should shardID and toShardID be explicitly encoded as 32-bit unsigned integers?
Let me know what the correct format is for these fields in signed RLP.
Thanks!
I’m not sure how to correctly encode the shardID and toShardID fields in this structure. Am I supposed to include them in the RLP fields when signing, and if yes — where, and in what format (32-bit integer, etc.)?
const fromAddress = Array.isArray(params.from) ? params.from[0] : params.from;
const toAddress = Array.isArray(params.to) ? params.to[0] : params.to;
const privateKey = privateKeys[fromAddress.address];
const valueBuf = toBufferFromNumber(BigInt(toAddress.value));
const gasPriceBuf = toBufferFromNumber(BigInt(params.gasPrice.toString()));
const gasLimitBuf = toBufferFromNumber(BigInt(params.gasLimit.toString()));
const nonceBuf = toBufferFromNumber(BigInt(params.nonce.toString()));
const toBuf = hexAddressToBuffer(toAddress.address);
const dataBuf = Buffer.from([]);
const chainIdNum = 1666700000n;
const chainIdBuf = toBufferFromNumber(chainIdNum);
const zeroBuf = Buffer.from([]);
const rawFieldsForHash = [
nonceBuf,
gasPriceBuf,
gasLimitBuf,
toBuf,
valueBuf,
dataBuf,
chainIdBuf,
zeroBuf,
zeroBuf,
];
const encodedForHash = RLP.encode(rawFieldsForHash);
const encodedForHashBuf = Buffer.from(encodedForHash);
const hashBuffer = Buffer.from(
keccak256('keccak256').update(encodedForHashBuf).digest()
);
const key = this.ec.keyFromPrivate(privateKey, 'hex');
const signature = key.sign(hashBuffer);
const rBuf = signature.r.toArrayLike(Buffer, 'be', 32);
const sBuf = signature.s.toArrayLike(Buffer, 'be', 32);
const vNum = chainIdNum * 2n + 35n + BigInt(signature.recoveryParam ?? 0);
const vBuf = toBufferFromNumber(vNum);
const signedFields = [
nonceBuf,
gasPriceBuf,
gasLimitBuf,
toBuf,
valueBuf,
dataBuf,
vBuf,
rBuf,
sBuf,
];
const rawSignedArr = RLP.encode(signedFields);
const rawSignedHex = '0x' + Buffer.from(rawSignedArr).toString('hex');
return { signedData: rawSignedHex };