Better transaction serializing API.

This commit is contained in:
Richard Moore 2018-07-12 02:42:46 -04:00
parent 4514229f27
commit e5d5871b95
No known key found for this signature in database
GPG Key ID: 525F70A6FCABC295
7 changed files with 113 additions and 53 deletions

View File

@ -9,6 +9,8 @@ import { Signature } from './secp256k1';
import errors = require('./errors');
export const AddressZero = '0x0000000000000000000000000000000000000000';
export const HashZero = '0x0000000000000000000000000000000000000000000000000000000000000000';
export type Arrayish = string | ArrayLike<number>;
@ -240,29 +242,52 @@ export function hexZeroPad(value: string, length: number): string {
return value;
}
export function splitSignature(signature: Arrayish): Signature {
let bytes: Uint8Array = arrayify(signature);
if (bytes.length !== 65) {
throw new Error('invalid signature');
}
function isSignature(value: any): value is Signature {
return (value && value.r != null && value.s != null);
}
var v = bytes[64];
if (v !== 27 && v !== 28) {
v = 27 + (v % 2);
export function splitSignature(signature: Arrayish | Signature): Signature {
let v = 0;
let r = '0x', s = '0x';
if (isSignature(signature)) {
r = hexZeroPad(signature.r, 32);
s = hexZeroPad(signature.s, 32);
let recoveryParam = signature.recoveryParam;
if (recoveryParam == null && signature.v != null) {
recoveryParam = 1 - (signature.v % 2);
}
v = 27 + recoveryParam;
} else {
let bytes: Uint8Array = arrayify(signature);
if (bytes.length !== 65) {
throw new Error('invalid signature');
}
r = hexlify(bytes.slice(0, 32));
s = hexlify(bytes.slice(32, 64));
v = bytes[64];
if (v !== 27 && v !== 28) {
v = 27 + (v % 2);
}
}
return {
r: hexlify(bytes.slice(0, 32)),
s: hexlify(bytes.slice(32, 64)),
r: r,
s: s,
recoveryParam: (v - 27),
v: v
}
}
export function joinSignature(signature: Signature): string {
signature = splitSignature(signature);
return hexlify(concat([
hexZeroPad(signature.r, 32),
hexZeroPad(signature.s, 32),
signature.r,
signature.s,
(signature.recoveryParam ? '0x1c': '0x1b')
]));
}

View File

@ -1,7 +1,7 @@
import { getAddress } from './address';
import { BigNumber, bigNumberify, BigNumberish,ConstantZero } from './bignumber';
import { arrayify, Arrayish, hexlify, hexZeroPad, stripZeros, } from './bytes';
import { BigNumber, bigNumberify, BigNumberish, ConstantZero } from './bignumber';
import { arrayify, Arrayish, hexlify, hexZeroPad, splitSignature, stripZeros, } from './bytes';
import { keccak256 } from './keccak256';
import { recoverAddress, Signature } from './secp256k1';
import * as RLP from './rlp';
@ -61,9 +61,7 @@ var transactionFields = [
];
export type SignDigestFunc = (digest: Uint8Array) => Signature;
export function serialize(transaction: UnsignedTransaction, signDigest?: SignDigestFunc): string {
export function serialize(transaction: UnsignedTransaction, signature?: Arrayish | Signature): string {
var raw: Array<string | Uint8Array> = [];
@ -87,20 +85,22 @@ export function serialize(transaction: UnsignedTransaction, signDigest?: SignDig
raw.push(hexlify(value));
});
if (transaction.chainId && transaction.chainId !== 0) {
if (transaction.chainId != null && transaction.chainId !== 0) {
raw.push(hexlify(transaction.chainId));
raw.push('0x');
raw.push('0x');
}
let unsignedTransaction = RLP.encode(raw);
// Requesting an unsigned transation
if (!signDigest) {
return RLP.encode(raw);
if (!signature) {
return unsignedTransaction;
}
var digest = keccak256(RLP.encode(raw));
var signature = signDigest(arrayify(digest));
// The splitSignature will ensure the transaction has a recoveryParam in the
// case that the signTransaction function only adds a v.
signature = splitSignature(signature);
// We pushed a chainId and null r, s on for hashing only; remove those
var v = 27 + signature.recoveryParam

View File

@ -69,9 +69,10 @@ export class Wallet extends Signer {
}
sign(transaction: TransactionRequest): Promise<string> {
return resolveProperties(transaction).then((tx) => {
return serializeTransaction(tx, this.signingKey.signDigest.bind(this.signingKey));
let rawTx = serializeTransaction(tx);
let signature = this.signingKey.signDigest(keccak256(rawTx));
return Promise.resolve(serializeTransaction(tx, signature));
});
}

View File

@ -119,12 +119,16 @@ describe('Test Transaction Signing and Parsing', function() {
assert.equal(parsedTransaction.chainId, 0, 'parses chainId (legacy)');
// Legacy serializes unsigned transaction
assert.equal(ethers.utils.serializeTransaction(transaction), test.unsignedTransaction,
'serializes undsigned transaction (legacy)');
{
let unsignedTx = ethers.utils.serializeTransaction(transaction);
assert.equal(unsignedTx, test.unsignedTransaction,
'serializes undsigned transaction (legacy)');
// Legacy signed serialized transaction
assert.equal(ethers.utils.serializeTransaction(transaction, signDigest), test.signedTransaction,
'signs transaction (legacy)');
// Legacy signed serialized transaction
let signature = signDigest(ethers.utils.keccak256(unsignedTx));
assert.equal(ethers.utils.serializeTransaction(transaction, signature), test.signedTransaction,
'signs transaction (legacy)');
}
// EIP155
@ -152,13 +156,18 @@ describe('Test Transaction Signing and Parsing', function() {
transaction.chainId = 5;
// EIP-155 signed serialized transaction
assert.equal(ethers.utils.serializeTransaction(transaction, signDigest), test.signedTransactionChainId5,
'signs transaction (eip155)');
{
// EIP-155 serialized unsigned transaction
let unsignedTx = ethers.utils.serializeTransaction(transaction);
assert.equal(unsignedTx, test.unsignedTransactionChainId5,
'serializes unsigned transaction (eip155) ');
// EIP-155 signed serialized transaction
let signature = signDigest(ethers.utils.keccak256(unsignedTx));
assert.equal(ethers.utils.serializeTransaction(transaction, signature), test.signedTransactionChainId5,
'signs transaction (eip155)');
}
// EIP-155 serialized unsigned transaction
assert.equal(ethers.utils.serializeTransaction(transaction), test.unsignedTransactionChainId5,
'serializes unsigned transaction (eip155) ');
});
});
});

View File

@ -5,6 +5,8 @@
*/
Object.defineProperty(exports, "__esModule", { value: true });
var errors = require("./errors");
exports.AddressZero = '0x0000000000000000000000000000000000000000';
exports.HashZero = '0x0000000000000000000000000000000000000000000000000000000000000000';
function isBigNumber(value) {
return !!value._bn;
}
@ -207,27 +209,46 @@ function hexZeroPad(value, length) {
return value;
}
exports.hexZeroPad = hexZeroPad;
function isSignature(value) {
return (value && value.r != null && value.s != null);
}
function splitSignature(signature) {
var bytes = arrayify(signature);
if (bytes.length !== 65) {
throw new Error('invalid signature');
var v = 0;
var r = '0x', s = '0x';
if (isSignature(signature)) {
r = hexZeroPad(signature.r, 32);
s = hexZeroPad(signature.s, 32);
var recoveryParam = signature.recoveryParam;
if (recoveryParam == null && signature.v != null) {
recoveryParam = 1 - (signature.v % 2);
}
v = 27 + recoveryParam;
}
var v = bytes[64];
if (v !== 27 && v !== 28) {
v = 27 + (v % 2);
else {
var bytes = arrayify(signature);
if (bytes.length !== 65) {
throw new Error('invalid signature');
}
r = hexlify(bytes.slice(0, 32));
s = hexlify(bytes.slice(32, 64));
v = bytes[64];
if (v !== 27 && v !== 28) {
v = 27 + (v % 2);
}
}
return {
r: hexlify(bytes.slice(0, 32)),
s: hexlify(bytes.slice(32, 64)),
r: r,
s: s,
recoveryParam: (v - 27),
v: v
};
}
exports.splitSignature = splitSignature;
function joinSignature(signature) {
signature = splitSignature(signature);
return hexlify(concat([
hexZeroPad(signature.r, 32),
hexZeroPad(signature.s, 32),
signature.r,
signature.s,
(signature.recoveryParam ? '0x1c' : '0x1b')
]));
}

View File

@ -34,7 +34,7 @@ var transactionFields = [
{ name: 'value', maxLength: 32 },
{ name: 'data' },
];
function serialize(transaction, signDigest) {
function serialize(transaction, signature) {
var raw = [];
transactionFields.forEach(function (fieldInfo) {
var value = transaction[fieldInfo.name] || ([]);
@ -52,17 +52,19 @@ function serialize(transaction, signDigest) {
}
raw.push(bytes_1.hexlify(value));
});
if (transaction.chainId && transaction.chainId !== 0) {
if (transaction.chainId != null && transaction.chainId !== 0) {
raw.push(bytes_1.hexlify(transaction.chainId));
raw.push('0x');
raw.push('0x');
}
var unsignedTransaction = RLP.encode(raw);
// Requesting an unsigned transation
if (!signDigest) {
return RLP.encode(raw);
if (!signature) {
return unsignedTransaction;
}
var digest = keccak256_1.keccak256(RLP.encode(raw));
var signature = signDigest(bytes_1.arrayify(digest));
// The splitSignature will ensure the transaction has a recoveryParam in the
// case that the signTransaction function only adds a v.
signature = bytes_1.splitSignature(signature);
// We pushed a chainId and null r, s on for hashing only; remove those
var v = 27 + signature.recoveryParam;
if (raw.length === 9) {

View File

@ -80,7 +80,9 @@ var Wallet = /** @class */ (function (_super) {
Wallet.prototype.sign = function (transaction) {
var _this = this;
return properties_1.resolveProperties(transaction).then(function (tx) {
return transaction_1.serialize(tx, _this.signingKey.signDigest.bind(_this.signingKey));
var rawTx = transaction_1.serialize(tx);
var signature = _this.signingKey.signDigest(keccak256_1.keccak256(rawTx));
return Promise.resolve(transaction_1.serialize(tx, signature));
});
};
Wallet.prototype.signMessage = function (message) {