-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3012eb4
commit 0d9f79d
Showing
4 changed files
with
52 additions
and
204 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,98 +1,52 @@ | ||
import { inject, injectable, tagged } from "@mainsail/container"; | ||
import { inject, injectable } from "@mainsail/container"; | ||
import { Contracts, Identifiers } from "@mainsail/contracts"; | ||
import { BigNumber, ByteBuffer } from "@mainsail/utils"; | ||
import { BigNumber } from "@mainsail/utils"; | ||
import { decodeRlp } from "ethers"; | ||
|
||
@injectable() | ||
export class Deserializer implements Contracts.Crypto.TransactionDeserializer { | ||
@inject(Identifiers.Cryptography.Transaction.TypeFactory) | ||
private readonly transactionTypeFactory!: Contracts.Transactions.TransactionTypeFactory; | ||
|
||
@inject(Identifiers.Cryptography.Identity.Address.Serializer) | ||
private readonly addressSerializer!: Contracts.Crypto.AddressSerializer; | ||
public async deserialize(serialized: Buffer | string): Promise<Contracts.Crypto.Transaction> { | ||
const data = {} as Contracts.Crypto.TransactionData; | ||
|
||
@inject(Identifiers.Cryptography.Identity.Address.Factory) | ||
private readonly addressFactory!: Contracts.Crypto.AddressFactory; | ||
const encodedRlp = | ||
"0x" + (typeof serialized === "string" ? serialized.slice(2) : serialized.toString("hex").slice(2)); | ||
|
||
@inject(Identifiers.Cryptography.Signature.Size) | ||
@tagged("type", "wallet") | ||
private readonly signatureSize!: number; | ||
const decoded = decodeRlp(encodedRlp); | ||
|
||
public async deserialize(serialized: string | Buffer): Promise<Contracts.Crypto.Transaction> { | ||
const data = {} as Contracts.Crypto.TransactionData; | ||
data.network = Number(decoded[0]); | ||
data.nonce = BigNumber.make(this.#parseNumber(decoded[1].toString())); | ||
data.gasPrice = this.#parseNumber(decoded[3].toString()); | ||
data.gasLimit = this.#parseNumber(decoded[4].toString()); | ||
data.recipientAddress = this.#parseAddress(decoded[5].toString()); | ||
data.value = BigNumber.make(this.#parseNumber(decoded[6].toString())); | ||
data.data = this.#parseData(decoded[7].toString()); | ||
|
||
const buff: ByteBuffer = this.#getByteBuffer(serialized); | ||
this.deserializeCommon(data, buff); | ||
if (decoded.length === 12) { | ||
data.v = this.#parseNumber(decoded[9].toString()) + 27; | ||
data.r = decoded[10].toString().slice(2); | ||
data.s = decoded[11].toString().slice(2); | ||
} | ||
|
||
const instance: Contracts.Crypto.Transaction = this.transactionTypeFactory.create(data); | ||
await this.#deserializeBody(instance.data, buff); | ||
|
||
this.#deserializeSignatures(instance.data, buff); | ||
|
||
instance.serialized = buff.getResult(); | ||
const eip1559Prefix = "02"; // marker for Type 2 (EIP1559) transaction which is the standard nowadays | ||
instance.serialized = Buffer.from(`${eip1559Prefix}${encodedRlp.slice(2)}`, "hex"); | ||
|
||
return instance; | ||
} | ||
|
||
public deserializeCommon(transaction: Contracts.Crypto.TransactionData, buf: ByteBuffer): void { | ||
transaction.network = buf.readUint8(); | ||
transaction.nonce = BigNumber.make(buf.readUint64()); | ||
transaction.gasPrice = buf.readUint32(); | ||
transaction.gasLimit = buf.readUint32(); | ||
transaction.value = BigNumber.ZERO; | ||
#parseNumber(value: string): number { | ||
return value === "0x" ? 0 : Number(value); | ||
} | ||
|
||
async #deserializeBody(transaction: Contracts.Crypto.TransactionData, buf: ByteBuffer): Promise<void> { | ||
transaction.value = BigNumber.make(buf.readUint256()); | ||
|
||
const recipientMarker = buf.readUint8(); | ||
if (recipientMarker === 1) { | ||
transaction.recipientAddress = await this.addressFactory.fromBuffer( | ||
this.addressSerializer.deserialize(buf), | ||
); | ||
} | ||
|
||
const dataLength = buf.readUint32(); | ||
const dataBytes = buf.readBytes(dataLength); | ||
|
||
transaction.data = dataBytes.toString("hex"); | ||
#parseAddress(value: string): string | undefined { | ||
return value === "0x" ? undefined : value; | ||
} | ||
|
||
#deserializeSignatures(transaction: Contracts.Crypto.TransactionData, buf: ByteBuffer): void { | ||
if (buf.getRemainderLength() && buf.getRemainderLength() % this.signatureSize === 0) { | ||
transaction.v = buf.readUint8(); | ||
transaction.r = buf.readBytes(32).toString("hex"); | ||
transaction.s = buf.readBytes(32).toString("hex"); | ||
} | ||
|
||
// if (buf.getRemainderLength()) { | ||
// if (buf.getRemainderLength() % (this.signatureSize + 1) === 0) { | ||
// transaction.signatures = []; | ||
|
||
// const count: number = buf.getRemainderLength() / (this.signatureSize + 1); | ||
// const publicKeyIndexes: { [index: number]: boolean } = {}; | ||
// for (let index = 0; index < count; index++) { | ||
// const multiSignaturePart: string = buf.readBytes(this.signatureSize + 1).toString("hex"); | ||
// const publicKeyIndex: number = Number.parseInt(multiSignaturePart.slice(0, 2), 16); | ||
|
||
// if (!publicKeyIndexes[publicKeyIndex]) { | ||
// publicKeyIndexes[publicKeyIndex] = true; | ||
// } else { | ||
// throw new Exceptions.DuplicateParticipantInMultiSignatureError(); | ||
// } | ||
|
||
// transaction.signatures.push(multiSignaturePart); | ||
// } | ||
// } else { | ||
// throw new Exceptions.InvalidTransactionBytesError("signature buffer not exhausted"); | ||
// } | ||
// } | ||
} | ||
|
||
#getByteBuffer(serialized: Buffer | string): ByteBuffer { | ||
if (!(serialized instanceof Buffer)) { | ||
serialized = Buffer.from(serialized, "hex"); | ||
} | ||
|
||
return ByteBuffer.fromBuffer(serialized); | ||
#parseData(value: string): string { | ||
return value === "0x" ? "" : value; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,139 +1,35 @@ | ||
import { inject, injectable, tagged } from "@mainsail/container"; | ||
import { Contracts, Exceptions, Identifiers } from "@mainsail/contracts"; | ||
import { ByteBuffer } from "@mainsail/utils"; | ||
import { injectable } from "@mainsail/container"; | ||
import { Contracts } from "@mainsail/contracts"; | ||
import { encodeRlp, toBeArray } from "ethers"; | ||
|
||
@injectable() | ||
export class Serializer implements Contracts.Crypto.TransactionSerializer { | ||
@inject(Identifiers.Cryptography.Configuration) | ||
private readonly configuration!: Contracts.Crypto.Configuration; | ||
|
||
@inject(Identifiers.Cryptography.Signature.Size) | ||
@tagged("type", "wallet") | ||
private readonly signatureSize!: number; | ||
|
||
@inject(Identifiers.Cryptography.Identity.Address.Factory) | ||
protected readonly addressFactory!: Contracts.Crypto.AddressFactory; | ||
|
||
@inject(Identifiers.Cryptography.Identity.Address.Serializer) | ||
private readonly addressSerializer!: Contracts.Crypto.AddressSerializer; | ||
|
||
@inject(Identifiers.Cryptography.Identity.Address.Size) | ||
private readonly addressSize!: number; | ||
|
||
public commonSize(transaction: Contracts.Crypto.Transaction): number { | ||
return ( | ||
1 + // network | ||
8 + // nonce | ||
4 + // gasLimit | ||
4 // gasPrice in gwei | ||
); | ||
} | ||
|
||
public signaturesSize( | ||
transaction: Contracts.Crypto.Transaction, | ||
options: Contracts.Crypto.SerializeOptions = {}, | ||
): number { | ||
let size = 0; | ||
|
||
const { data } = transaction; | ||
if (data.v && data.r && data.s && !options.excludeSignature) { | ||
size += this.signatureSize; | ||
} | ||
|
||
// if (data.signatures && !options.excludeMultiSignature) { | ||
// size += data.signatures.length * (1 + this.signatureSize) /* 1 additional byte for index */; | ||
// } | ||
|
||
return size; | ||
} | ||
|
||
public assetSize(transaction: Contracts.Crypto.Transaction): number { | ||
return ( | ||
32 + // value | ||
1 + // recipient marker | ||
(transaction.data.recipientAddress ? this.addressSize : 0) + // recipient | ||
4 + // payload length | ||
Buffer.byteLength(transaction.data.data, "hex") | ||
); | ||
} | ||
|
||
public totalSize( | ||
transaction: Contracts.Crypto.Transaction, | ||
options: Contracts.Crypto.SerializeOptions = {}, | ||
): number { | ||
return this.commonSize(transaction) + this.assetSize(transaction) + this.signaturesSize(transaction, options); | ||
} | ||
|
||
public async serialize( | ||
transaction: Contracts.Crypto.Transaction, | ||
options: Contracts.Crypto.SerializeOptions = {}, | ||
): Promise<Buffer> { | ||
const bufferSize = this.totalSize(transaction, options); | ||
const buff: ByteBuffer = ByteBuffer.fromSize(bufferSize); | ||
|
||
this.#serializeCommon(transaction.data, buff); | ||
|
||
const serialized = await this.#serializeBody(transaction.data); | ||
buff.writeBytes(serialized.getResult()); | ||
|
||
this.#serializeSignatures(transaction.data, buff, options); | ||
|
||
const bufferBuffer = buff.getResult(); | ||
if (bufferBuffer.length !== bufferSize) { | ||
throw new Exceptions.InvalidTransactionBytesError( | ||
`expected size ${bufferSize} actual size: ${bufferBuffer.length}`, | ||
); | ||
const fields = [ | ||
toBeArray(transaction.data.network), // chainId - 0 | ||
toBeArray(transaction.data.nonce.toBigInt()), // nonce - 1 | ||
toBeArray(0), // maxPriorityFeePerGas - 2 | ||
toBeArray(transaction.data.gasPrice), // maxFeePerGas - 3 | ||
toBeArray(transaction.data.gasLimit), // gasLimit - 4 | ||
transaction.data.recipientAddress || "0x", // to - 5 | ||
toBeArray(transaction.data.value.toBigInt()), // value - 6 | ||
transaction.data.data.startsWith("0x") ? transaction.data.data : `0x${transaction.data.data}`, // data - 7 | ||
[], //accessList - 8 | ||
]; | ||
|
||
if (transaction.data.v && transaction.data.r && transaction.data.s && !options.excludeSignature) { | ||
fields.push(toBeArray(transaction.data.v - 27), `0x${transaction.data.r}`, `0x${transaction.data.s}`); | ||
} | ||
|
||
transaction.serialized = bufferBuffer; | ||
|
||
return bufferBuffer; | ||
} | ||
|
||
#serializeCommon(transaction: Contracts.Crypto.TransactionData, buff: ByteBuffer): void { | ||
buff.writeUint8(transaction.network || this.configuration.get("network.pubKeyHash")); | ||
buff.writeUint64(transaction.nonce.toBigInt()); | ||
buff.writeUint32(transaction.gasPrice); | ||
buff.writeUint32(transaction.gasLimit); | ||
} | ||
|
||
async #serializeBody(transaction: Contracts.Crypto.TransactionData): Promise<ByteBuffer> { | ||
const dataBytes = Buffer.from(transaction.data, "hex"); | ||
const rlpEncoded = encodeRlp(fields); | ||
|
||
const recipient = transaction.recipientAddress; | ||
const eip1559Prefix = "02"; // marker for Type 2 (EIP1559) transaction which is the standard nowadays | ||
|
||
const buff: ByteBuffer = ByteBuffer.fromSize( | ||
32 + 1 + (recipient ? this.addressSize : 0) + 4 + dataBytes.byteLength, | ||
); | ||
|
||
buff.writeUint256(transaction.value.toBigInt()); | ||
|
||
if (recipient) { | ||
buff.writeUint8(1); | ||
this.addressSerializer.serialize(buff, await this.addressFactory.toBuffer(recipient)); | ||
} else { | ||
buff.writeUint8(0); | ||
} | ||
|
||
buff.writeUint32(dataBytes.byteLength); | ||
buff.writeBytes(dataBytes); | ||
|
||
return buff; | ||
} | ||
|
||
#serializeSignatures( | ||
transaction: Contracts.Crypto.TransactionData, | ||
buff: ByteBuffer, | ||
options: Contracts.Crypto.SerializeOptions = {}, | ||
): void { | ||
if (transaction.v && transaction.r && transaction.s && !options.excludeSignature) { | ||
buff.writeUint8(transaction.v); | ||
buff.writeBytes(Buffer.from(transaction.r, "hex")); | ||
buff.writeBytes(Buffer.from(transaction.s, "hex")); | ||
} | ||
transaction.serialized = Buffer.from(`${eip1559Prefix}${rlpEncoded.slice(2)}`, "hex"); | ||
|
||
// if (transaction.signatures && !options.excludeMultiSignature) { | ||
// buff.writeBytes(Buffer.from(transaction.signatures.join(""), "hex")); | ||
// } | ||
return transaction.serialized; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters