Better transaction serializing API.
This commit is contained in:
parent
4514229f27
commit
e5d5871b95
@ -9,6 +9,8 @@ import { Signature } from './secp256k1';
|
|||||||
import errors = require('./errors');
|
import errors = require('./errors');
|
||||||
|
|
||||||
|
|
||||||
|
export const AddressZero = '0x0000000000000000000000000000000000000000';
|
||||||
|
export const HashZero = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
|
|
||||||
export type Arrayish = string | ArrayLike<number>;
|
export type Arrayish = string | ArrayLike<number>;
|
||||||
|
|
||||||
@ -240,29 +242,52 @@ export function hexZeroPad(value: string, length: number): string {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function splitSignature(signature: Arrayish): Signature {
|
function isSignature(value: any): value is Signature {
|
||||||
let bytes: Uint8Array = arrayify(signature);
|
return (value && value.r != null && value.s != null);
|
||||||
if (bytes.length !== 65) {
|
}
|
||||||
throw new Error('invalid signature');
|
|
||||||
}
|
|
||||||
|
|
||||||
var v = bytes[64];
|
export function splitSignature(signature: Arrayish | Signature): Signature {
|
||||||
if (v !== 27 && v !== 28) {
|
let v = 0;
|
||||||
v = 27 + (v % 2);
|
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 {
|
return {
|
||||||
r: hexlify(bytes.slice(0, 32)),
|
r: r,
|
||||||
s: hexlify(bytes.slice(32, 64)),
|
s: s,
|
||||||
recoveryParam: (v - 27),
|
recoveryParam: (v - 27),
|
||||||
v: v
|
v: v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function joinSignature(signature: Signature): string {
|
export function joinSignature(signature: Signature): string {
|
||||||
|
signature = splitSignature(signature);
|
||||||
|
|
||||||
return hexlify(concat([
|
return hexlify(concat([
|
||||||
hexZeroPad(signature.r, 32),
|
signature.r,
|
||||||
hexZeroPad(signature.s, 32),
|
signature.s,
|
||||||
(signature.recoveryParam ? '0x1c': '0x1b')
|
(signature.recoveryParam ? '0x1c': '0x1b')
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import { getAddress } from './address';
|
import { getAddress } from './address';
|
||||||
import { BigNumber, bigNumberify, BigNumberish,ConstantZero } from './bignumber';
|
import { BigNumber, bigNumberify, BigNumberish, ConstantZero } from './bignumber';
|
||||||
import { arrayify, Arrayish, hexlify, hexZeroPad, stripZeros, } from './bytes';
|
import { arrayify, Arrayish, hexlify, hexZeroPad, splitSignature, stripZeros, } from './bytes';
|
||||||
import { keccak256 } from './keccak256';
|
import { keccak256 } from './keccak256';
|
||||||
import { recoverAddress, Signature } from './secp256k1';
|
import { recoverAddress, Signature } from './secp256k1';
|
||||||
import * as RLP from './rlp';
|
import * as RLP from './rlp';
|
||||||
@ -61,9 +61,7 @@ var transactionFields = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
export type SignDigestFunc = (digest: Uint8Array) => Signature;
|
export function serialize(transaction: UnsignedTransaction, signature?: Arrayish | Signature): string {
|
||||||
|
|
||||||
export function serialize(transaction: UnsignedTransaction, signDigest?: SignDigestFunc): string {
|
|
||||||
|
|
||||||
var raw: Array<string | Uint8Array> = [];
|
var raw: Array<string | Uint8Array> = [];
|
||||||
|
|
||||||
@ -87,20 +85,22 @@ export function serialize(transaction: UnsignedTransaction, signDigest?: SignDig
|
|||||||
raw.push(hexlify(value));
|
raw.push(hexlify(value));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (transaction.chainId && transaction.chainId !== 0) {
|
if (transaction.chainId != null && transaction.chainId !== 0) {
|
||||||
raw.push(hexlify(transaction.chainId));
|
raw.push(hexlify(transaction.chainId));
|
||||||
raw.push('0x');
|
raw.push('0x');
|
||||||
raw.push('0x');
|
raw.push('0x');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let unsignedTransaction = RLP.encode(raw);
|
||||||
|
|
||||||
// Requesting an unsigned transation
|
// Requesting an unsigned transation
|
||||||
if (!signDigest) {
|
if (!signature) {
|
||||||
return RLP.encode(raw);
|
return unsignedTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
var digest = keccak256(RLP.encode(raw));
|
// The splitSignature will ensure the transaction has a recoveryParam in the
|
||||||
|
// case that the signTransaction function only adds a v.
|
||||||
var signature = signDigest(arrayify(digest));
|
signature = splitSignature(signature);
|
||||||
|
|
||||||
// We pushed a chainId and null r, s on for hashing only; remove those
|
// We pushed a chainId and null r, s on for hashing only; remove those
|
||||||
var v = 27 + signature.recoveryParam
|
var v = 27 + signature.recoveryParam
|
||||||
|
@ -69,9 +69,10 @@ export class Wallet extends Signer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sign(transaction: TransactionRequest): Promise<string> {
|
sign(transaction: TransactionRequest): Promise<string> {
|
||||||
|
|
||||||
return resolveProperties(transaction).then((tx) => {
|
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));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,12 +119,16 @@ describe('Test Transaction Signing and Parsing', function() {
|
|||||||
assert.equal(parsedTransaction.chainId, 0, 'parses chainId (legacy)');
|
assert.equal(parsedTransaction.chainId, 0, 'parses chainId (legacy)');
|
||||||
|
|
||||||
// Legacy serializes unsigned transaction
|
// 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
|
// Legacy signed serialized transaction
|
||||||
assert.equal(ethers.utils.serializeTransaction(transaction, signDigest), test.signedTransaction,
|
let signature = signDigest(ethers.utils.keccak256(unsignedTx));
|
||||||
'signs transaction (legacy)');
|
assert.equal(ethers.utils.serializeTransaction(transaction, signature), test.signedTransaction,
|
||||||
|
'signs transaction (legacy)');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// EIP155
|
// EIP155
|
||||||
@ -152,13 +156,18 @@ describe('Test Transaction Signing and Parsing', function() {
|
|||||||
|
|
||||||
transaction.chainId = 5;
|
transaction.chainId = 5;
|
||||||
|
|
||||||
// EIP-155 signed serialized transaction
|
{
|
||||||
assert.equal(ethers.utils.serializeTransaction(transaction, signDigest), test.signedTransactionChainId5,
|
// EIP-155 serialized unsigned transaction
|
||||||
'signs transaction (eip155)');
|
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) ');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
var errors = require("./errors");
|
var errors = require("./errors");
|
||||||
|
exports.AddressZero = '0x0000000000000000000000000000000000000000';
|
||||||
|
exports.HashZero = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
function isBigNumber(value) {
|
function isBigNumber(value) {
|
||||||
return !!value._bn;
|
return !!value._bn;
|
||||||
}
|
}
|
||||||
@ -207,27 +209,46 @@ function hexZeroPad(value, length) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
exports.hexZeroPad = hexZeroPad;
|
exports.hexZeroPad = hexZeroPad;
|
||||||
|
function isSignature(value) {
|
||||||
|
return (value && value.r != null && value.s != null);
|
||||||
|
}
|
||||||
function splitSignature(signature) {
|
function splitSignature(signature) {
|
||||||
var bytes = arrayify(signature);
|
var v = 0;
|
||||||
if (bytes.length !== 65) {
|
var r = '0x', s = '0x';
|
||||||
throw new Error('invalid signature');
|
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];
|
else {
|
||||||
if (v !== 27 && v !== 28) {
|
var bytes = arrayify(signature);
|
||||||
v = 27 + (v % 2);
|
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 {
|
return {
|
||||||
r: hexlify(bytes.slice(0, 32)),
|
r: r,
|
||||||
s: hexlify(bytes.slice(32, 64)),
|
s: s,
|
||||||
recoveryParam: (v - 27),
|
recoveryParam: (v - 27),
|
||||||
v: v
|
v: v
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
exports.splitSignature = splitSignature;
|
exports.splitSignature = splitSignature;
|
||||||
function joinSignature(signature) {
|
function joinSignature(signature) {
|
||||||
|
signature = splitSignature(signature);
|
||||||
return hexlify(concat([
|
return hexlify(concat([
|
||||||
hexZeroPad(signature.r, 32),
|
signature.r,
|
||||||
hexZeroPad(signature.s, 32),
|
signature.s,
|
||||||
(signature.recoveryParam ? '0x1c' : '0x1b')
|
(signature.recoveryParam ? '0x1c' : '0x1b')
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ var transactionFields = [
|
|||||||
{ name: 'value', maxLength: 32 },
|
{ name: 'value', maxLength: 32 },
|
||||||
{ name: 'data' },
|
{ name: 'data' },
|
||||||
];
|
];
|
||||||
function serialize(transaction, signDigest) {
|
function serialize(transaction, signature) {
|
||||||
var raw = [];
|
var raw = [];
|
||||||
transactionFields.forEach(function (fieldInfo) {
|
transactionFields.forEach(function (fieldInfo) {
|
||||||
var value = transaction[fieldInfo.name] || ([]);
|
var value = transaction[fieldInfo.name] || ([]);
|
||||||
@ -52,17 +52,19 @@ function serialize(transaction, signDigest) {
|
|||||||
}
|
}
|
||||||
raw.push(bytes_1.hexlify(value));
|
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(bytes_1.hexlify(transaction.chainId));
|
||||||
raw.push('0x');
|
raw.push('0x');
|
||||||
raw.push('0x');
|
raw.push('0x');
|
||||||
}
|
}
|
||||||
|
var unsignedTransaction = RLP.encode(raw);
|
||||||
// Requesting an unsigned transation
|
// Requesting an unsigned transation
|
||||||
if (!signDigest) {
|
if (!signature) {
|
||||||
return RLP.encode(raw);
|
return unsignedTransaction;
|
||||||
}
|
}
|
||||||
var digest = keccak256_1.keccak256(RLP.encode(raw));
|
// The splitSignature will ensure the transaction has a recoveryParam in the
|
||||||
var signature = signDigest(bytes_1.arrayify(digest));
|
// 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
|
// We pushed a chainId and null r, s on for hashing only; remove those
|
||||||
var v = 27 + signature.recoveryParam;
|
var v = 27 + signature.recoveryParam;
|
||||||
if (raw.length === 9) {
|
if (raw.length === 9) {
|
||||||
|
@ -80,7 +80,9 @@ var Wallet = /** @class */ (function (_super) {
|
|||||||
Wallet.prototype.sign = function (transaction) {
|
Wallet.prototype.sign = function (transaction) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
return properties_1.resolveProperties(transaction).then(function (tx) {
|
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) {
|
Wallet.prototype.signMessage = function (message) {
|
||||||
|
Loading…
Reference in New Issue
Block a user