From 750fa41eaa2aec111bf1d3e3cb34840f5dca6e56 Mon Sep 17 00:00:00 2001 From: schimih Date: Mon, 12 Sep 2022 12:25:37 +0300 Subject: [PATCH] added guardian support --- src/address.ts | 7 ++++ src/constants.ts | 6 ++- src/index.ts | 1 + src/interface.ts | 2 + src/networkParams.spec.ts | 10 ++--- src/networkParams.ts | 23 +++++++++-- src/proto/compiled.d.ts | 12 ++++++ src/proto/compiled.js | 62 +++++++++++++++++++++++++++ src/proto/serializer.spec.ts | 10 ++--- src/proto/serializer.ts | 31 ++++++++++---- src/proto/transaction.proto | 28 +++++++------ src/smartcontracts/interaction.ts | 12 +++++- src/transaction.spec.ts | 23 ++++++++++- src/transaction.ts | 69 +++++++++++++++++++++++++++---- 14 files changed, 249 insertions(+), 47 deletions(-) diff --git a/src/address.ts b/src/address.ts index b548f3a7..ba840e8d 100644 --- a/src/address.ts +++ b/src/address.ts @@ -90,6 +90,13 @@ export class Address { return Address.fromValidHex(value); } + /** + * Creates an empty address object + */ + static empty(): Address { + return new Address(); + } + /** * Creates an address object from a bech32-encoded string */ diff --git a/src/constants.ts b/src/constants.ts index eeb3a6dd..c5c3390d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,10 +1,14 @@ export const TRANSACTION_MIN_GAS_PRICE = 1000000000; export const TRANSACTION_OPTIONS_DEFAULT = 0; export const TRANSACTION_OPTIONS_TX_HASH_SIGN = 1; +export const TRANSACTION_OPTIONS_TX_GUARDED = 2; export const TRANSACTION_VERSION_DEFAULT = 1; -export const TRANSACTION_VERSION_TX_HASH_SIGN = 2; +export const TRANSACTION_VERSION_TX_OPTIONS = 2; export const ESDT_TRANSFER_GAS_LIMIT = 500000; export const ESDT_TRANSFER_FUNCTION_NAME = "ESDTTransfer"; export const ESDTNFT_TRANSFER_FUNCTION_NAME = "ESDTNFTTransfer"; export const MULTI_ESDTNFT_TRANSFER_FUNCTION_NAME = "MultiESDTNFTTransfer"; export const ESDT_TRANSFER_VALUE = "0"; + +// Masks +export const TRANSACTION_OPTIONS_TX_GUARDED_MASK = 0b0010 diff --git a/src/index.ts b/src/index.ts index 6fe6acdc..92230f7b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,3 +18,4 @@ export * from "./utils"; export * from "./tokenPayment"; export * from "./tokenTransferBuilders"; export * from "./smartcontracts"; +export * from "./relayedTransactionV2Builder" diff --git a/src/interface.ts b/src/interface.ts index 1e0fdaeb..21eb631d 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -13,6 +13,7 @@ export interface IPlainTransactionObject { value: string; receiver: string; sender: string; + guardian?: string; gasPrice: number; gasLimit: number; data?: string; @@ -20,6 +21,7 @@ export interface IPlainTransactionObject { version: number; options?: number; signature?: string; + guardianSignature?: string; } export interface ISignature { hex(): string; } diff --git a/src/networkParams.spec.ts b/src/networkParams.spec.ts index 774d9651..63fac8cb 100644 --- a/src/networkParams.spec.ts +++ b/src/networkParams.spec.ts @@ -1,10 +1,10 @@ -import {assert} from "chai"; -import {TransactionOptions, TransactionVersion} from "./networkParams"; +import { assert } from "chai"; +import { TransactionOptions, TransactionVersion } from "./networkParams"; import { TRANSACTION_OPTIONS_DEFAULT, TRANSACTION_OPTIONS_TX_HASH_SIGN, TRANSACTION_VERSION_DEFAULT, - TRANSACTION_VERSION_TX_HASH_SIGN + TRANSACTION_VERSION_TX_OPTIONS } from "./constants"; describe("test transaction version", () => { @@ -16,10 +16,10 @@ describe("test transaction version", () => { it("should init with correct numeric values based on static constructors", () => { let txVersionDefault = TransactionVersion.withDefaultVersion(); - let txVersionTxHashSign = TransactionVersion.withTxHashSignVersion(); + let txVersionTxHashSign = TransactionVersion.withTxOptions(); assert.equal(TRANSACTION_VERSION_DEFAULT, txVersionDefault.valueOf()); - assert.equal(TRANSACTION_VERSION_TX_HASH_SIGN, txVersionTxHashSign.valueOf()); + assert.equal(TRANSACTION_VERSION_TX_OPTIONS, txVersionTxHashSign.valueOf()); }); }); diff --git a/src/networkParams.ts b/src/networkParams.ts index 651496be..954f1815 100644 --- a/src/networkParams.ts +++ b/src/networkParams.ts @@ -2,7 +2,7 @@ import * as errors from "./errors"; import { TRANSACTION_OPTIONS_DEFAULT, TRANSACTION_OPTIONS_TX_HASH_SIGN, - TRANSACTION_VERSION_DEFAULT, TRANSACTION_VERSION_TX_HASH_SIGN + TRANSACTION_VERSION_DEFAULT, TRANSACTION_VERSION_TX_OPTIONS, TRANSACTION_OPTIONS_TX_GUARDED, TRANSACTION_OPTIONS_TX_GUARDED_MASK } from "./constants"; export class TransactionVersion { @@ -34,8 +34,8 @@ export class TransactionVersion { /** * Creates a TransactionVersion object with the VERSION setting for hash signing */ - static withTxHashSignVersion(): TransactionVersion { - return new TransactionVersion(TRANSACTION_VERSION_TX_HASH_SIGN); + static withTxOptions(): TransactionVersion { + return new TransactionVersion(TRANSACTION_VERSION_TX_OPTIONS); } valueOf(): number { @@ -76,6 +76,23 @@ export class TransactionOptions { return new TransactionOptions(TRANSACTION_OPTIONS_TX_HASH_SIGN); } + /** + * Created a TransactionsOptions object with the setting for guarded transaction + */ + static withTxGuardedOptions(): TransactionOptions { + return new TransactionOptions(TRANSACTION_OPTIONS_TX_GUARDED); + } + + /** + * Returns true if Guarded Transaction Option is set + */ + isGuardedTx(): boolean { + if ((this.value & TRANSACTION_OPTIONS_TX_GUARDED_MASK) == TRANSACTION_OPTIONS_TX_GUARDED) { + return true + } + return false + } + valueOf(): number { return this.value; } diff --git a/src/proto/compiled.d.ts b/src/proto/compiled.d.ts index 4b2983a0..ed08acfe 100644 --- a/src/proto/compiled.d.ts +++ b/src/proto/compiled.d.ts @@ -43,6 +43,12 @@ export namespace proto { /** Transaction Options */ Options?: (number|null); + + /** Transaction GuardAddr */ + GuardAddr?: (Uint8Array|null); + + /** Transaction GuardSignature */ + GuardSignature?: (Uint8Array|null); } /** Represents a Transaction. */ @@ -93,6 +99,12 @@ export namespace proto { /** Transaction Options. */ public Options: number; + /** Transaction GuardAddr. */ + public GuardAddr: Uint8Array; + + /** Transaction GuardSignature. */ + public GuardSignature: Uint8Array; + /** * Creates a new Transaction instance using the specified properties. * @param [properties] Properties to set diff --git a/src/proto/compiled.js b/src/proto/compiled.js index 50829ab8..7efef837 100644 --- a/src/proto/compiled.js +++ b/src/proto/compiled.js @@ -37,6 +37,8 @@ $root.proto = (function() { * @property {number|null} [Version] Transaction Version * @property {Uint8Array|null} [Signature] Transaction Signature * @property {number|null} [Options] Transaction Options + * @property {Uint8Array|null} [GuardAddr] Transaction GuardAddr + * @property {Uint8Array|null} [GuardSignature] Transaction GuardSignature */ /** @@ -158,6 +160,22 @@ $root.proto = (function() { */ Transaction.prototype.Options = 0; + /** + * Transaction GuardAddr. + * @member {Uint8Array} GuardAddr + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.GuardAddr = $util.newBuffer([]); + + /** + * Transaction GuardSignature. + * @member {Uint8Array} GuardSignature + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.GuardSignature = $util.newBuffer([]); + /** * Creates a new Transaction instance using the specified properties. * @function create @@ -208,6 +226,10 @@ $root.proto = (function() { writer.uint32(/* id 12, wireType 2 =*/98).bytes(message.Signature); if (message.Options != null && Object.hasOwnProperty.call(message, "Options")) writer.uint32(/* id 13, wireType 0 =*/104).uint32(message.Options); + if (message.GuardAddr != null && Object.hasOwnProperty.call(message, "GuardAddr")) + writer.uint32(/* id 14, wireType 2 =*/114).bytes(message.GuardAddr); + if (message.GuardSignature != null && Object.hasOwnProperty.call(message, "GuardSignature")) + writer.uint32(/* id 15, wireType 2 =*/122).bytes(message.GuardSignature); return writer; }; @@ -281,6 +303,12 @@ $root.proto = (function() { case 13: message.Options = reader.uint32(); break; + case 14: + message.GuardAddr = reader.bytes(); + break; + case 15: + message.GuardSignature = reader.bytes(); + break; default: reader.skipType(tag & 7); break; @@ -355,6 +383,12 @@ $root.proto = (function() { if (message.Options != null && message.hasOwnProperty("Options")) if (!$util.isInteger(message.Options)) return "Options: integer expected"; + if (message.GuardAddr != null && message.hasOwnProperty("GuardAddr")) + if (!(message.GuardAddr && typeof message.GuardAddr.length === "number" || $util.isString(message.GuardAddr))) + return "GuardAddr: buffer expected"; + if (message.GuardSignature != null && message.hasOwnProperty("GuardSignature")) + if (!(message.GuardSignature && typeof message.GuardSignature.length === "number" || $util.isString(message.GuardSignature))) + return "GuardSignature: buffer expected"; return null; }; @@ -441,6 +475,16 @@ $root.proto = (function() { message.Signature = object.Signature; if (object.Options != null) message.Options = object.Options >>> 0; + if (object.GuardAddr != null) + if (typeof object.GuardAddr === "string") + $util.base64.decode(object.GuardAddr, message.GuardAddr = $util.newBuffer($util.base64.length(object.GuardAddr)), 0); + else if (object.GuardAddr.length) + message.GuardAddr = object.GuardAddr; + if (object.GuardSignature != null) + if (typeof object.GuardSignature === "string") + $util.base64.decode(object.GuardSignature, message.GuardSignature = $util.newBuffer($util.base64.length(object.GuardSignature)), 0); + else if (object.GuardSignature.length) + message.GuardSignature = object.GuardSignature; return message; }; @@ -531,6 +575,20 @@ $root.proto = (function() { object.Signature = $util.newBuffer(object.Signature); } object.Options = 0; + if (options.bytes === String) + object.GuardAddr = ""; + else { + object.GuardAddr = []; + if (options.bytes !== Array) + object.GuardAddr = $util.newBuffer(object.GuardAddr); + } + if (options.bytes === String) + object.GuardSignature = ""; + else { + object.GuardSignature = []; + if (options.bytes !== Array) + object.GuardSignature = $util.newBuffer(object.GuardSignature); + } } if (message.Nonce != null && message.hasOwnProperty("Nonce")) if (typeof message.Nonce === "number") @@ -567,6 +625,10 @@ $root.proto = (function() { object.Signature = options.bytes === String ? $util.base64.encode(message.Signature, 0, message.Signature.length) : options.bytes === Array ? Array.prototype.slice.call(message.Signature) : message.Signature; if (message.Options != null && message.hasOwnProperty("Options")) object.Options = message.Options; + if (message.GuardAddr != null && message.hasOwnProperty("GuardAddr")) + object.GuardAddr = options.bytes === String ? $util.base64.encode(message.GuardAddr, 0, message.GuardAddr.length) : options.bytes === Array ? Array.prototype.slice.call(message.GuardAddr) : message.GuardAddr; + if (message.GuardSignature != null && message.hasOwnProperty("GuardSignature")) + object.GuardSignature = options.bytes === String ? $util.base64.encode(message.GuardSignature, 0, message.GuardSignature.length) : options.bytes === Array ? Array.prototype.slice.call(message.GuardSignature) : message.GuardSignature; return object; }; diff --git a/src/proto/serializer.spec.ts b/src/proto/serializer.spec.ts index 3ba47bbc..18ece6b9 100644 --- a/src/proto/serializer.spec.ts +++ b/src/proto/serializer.spec.ts @@ -26,7 +26,7 @@ describe("serialize transactions", () => { chainID: "local-testnet" }); - transaction.applySignature(new Signature("b56769014f2bdc5cf9fc4a05356807d71fcf8775c819b0f1b0964625b679c918ffa64862313bfef86f99b38cb84fcdb16fa33ad6eb565276616723405cd8f109"), wallets.alice.address); + transaction.applySignature(new Signature("b56769014f2bdc5cf9fc4a05356807d71fcf8775c819b0f1b0964625b679c918ffa64862313bfef86f99b38cb84fcdb16fa33ad6eb565276616723405cd8f109")); let buffer = serializer.serializeTransaction(transaction); assert.equal(buffer.toString("hex"), "0859120200001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc0340d08603520d6c6f63616c2d746573746e657458016240b56769014f2bdc5cf9fc4a05356807d71fcf8775c819b0f1b0964625b679c918ffa64862313bfef86f99b38cb84fcdb16fa33ad6eb565276616723405cd8f109"); @@ -43,7 +43,7 @@ describe("serialize transactions", () => { chainID: "local-testnet" }); - transaction.applySignature(new Signature("e47fd437fc17ac9a69f7bf5f85bafa9e7628d851c4f69bd9fedc7e36029708b2e6d168d5cd652ea78beedd06d4440974ca46c403b14071a1a148d4188f6f2c0d"), wallets.alice.address); + transaction.applySignature(new Signature("e47fd437fc17ac9a69f7bf5f85bafa9e7628d851c4f69bd9fedc7e36029708b2e6d168d5cd652ea78beedd06d4440974ca46c403b14071a1a148d4188f6f2c0d")); let buffer = serializer.serializeTransaction(transaction); assert.equal(buffer.toString("hex"), "085a120200001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc034080f1044a0568656c6c6f520d6c6f63616c2d746573746e657458016240e47fd437fc17ac9a69f7bf5f85bafa9e7628d851c4f69bd9fedc7e36029708b2e6d168d5cd652ea78beedd06d4440974ca46c403b14071a1a148d4188f6f2c0d"); @@ -60,7 +60,7 @@ describe("serialize transactions", () => { chainID: "local-testnet" }); - transaction.applySignature(new Signature("9074789e0b4f9b2ac24b1fd351a4dd840afcfeb427b0f93e2a2d429c28c65ee9f4c288ca4dbde79de0e5bcf8c1a5d26e1b1c86203faea923e0edefb0b5099b0c"), wallets.alice.address); + transaction.applySignature(new Signature("9074789e0b4f9b2ac24b1fd351a4dd840afcfeb427b0f93e2a2d429c28c65ee9f4c288ca4dbde79de0e5bcf8c1a5d26e1b1c86203faea923e0edefb0b5099b0c")); let buffer = serializer.serializeTransaction(transaction); assert.equal(buffer.toString("hex"), "085b1209008ac7230489e800001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc0340a08d064a0c666f722074686520626f6f6b520d6c6f63616c2d746573746e6574580162409074789e0b4f9b2ac24b1fd351a4dd840afcfeb427b0f93e2a2d429c28c65ee9f4c288ca4dbde79de0e5bcf8c1a5d26e1b1c86203faea923e0edefb0b5099b0c"); @@ -77,7 +77,7 @@ describe("serialize transactions", () => { chainID: "local-testnet" }); - transaction.applySignature(new Signature("39938d15812708475dfc8125b5d41dbcea0b2e3e7aabbbfceb6ce4f070de3033676a218b73facd88b1432d7d4accab89c6130b3abe5cc7bbbb5146e61d355b03"), wallets.alice.address); + transaction.applySignature(new Signature("39938d15812708475dfc8125b5d41dbcea0b2e3e7aabbbfceb6ce4f070de3033676a218b73facd88b1432d7d4accab89c6130b3abe5cc7bbbb5146e61d355b03")); let buffer = serializer.serializeTransaction(transaction); assert.equal(buffer.toString("hex"), "085c120e00018ee90ff6181f3761632000001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc0340a08d064a11666f722074686520737061636573686970520d6c6f63616c2d746573746e65745801624039938d15812708475dfc8125b5d41dbcea0b2e3e7aabbbfceb6ce4f070de3033676a218b73facd88b1432d7d4accab89c6130b3abe5cc7bbbb5146e61d355b03"); @@ -95,7 +95,7 @@ describe("serialize transactions", () => { version: new TransactionVersion(1) }); - transaction.applySignature(new Signature("dfa3e9f2fdec60dcb353bac3b3435b4a2ff251e7e98eaf8620f46c731fc70c8ba5615fd4e208b05e75fe0f7dc44b7a99567e29f94fcd91efac7e67b182cd2a04"), wallets.alice.address) + transaction.applySignature(new Signature("dfa3e9f2fdec60dcb353bac3b3435b4a2ff251e7e98eaf8620f46c731fc70c8ba5615fd4e208b05e75fe0f7dc44b7a99567e29f94fcd91efac7e67b182cd2a04")) let buffer = serializer.serializeTransaction(transaction); assert.equal(buffer.toString("hex"), "120200001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc034080f1044a0568656c6c6f520d6c6f63616c2d746573746e657458016240dfa3e9f2fdec60dcb353bac3b3435b4a2ff251e7e98eaf8620f46c731fc70c8ba5615fd4e208b05e75fe0f7dc44b7a99567e29f94fcd91efac7e67b182cd2a04"); diff --git a/src/proto/serializer.ts b/src/proto/serializer.ts index 33b9c763..12ddd08c 100644 --- a/src/proto/serializer.ts +++ b/src/proto/serializer.ts @@ -2,9 +2,9 @@ import * as errors from "../errors"; import { bigIntToBuffer } from "../smartcontracts/codec/utils"; import { Transaction } from "../transaction"; import { proto } from "./compiled"; -import {TRANSACTION_OPTIONS_DEFAULT} from "../constants"; +import { TRANSACTION_OPTIONS_DEFAULT, TRANSACTION_OPTIONS_TX_GUARDED } from "../constants"; import { Address } from "../address"; -import { ITransactionValue } from "../interface"; +import { IAddress, ITransactionValue } from "../interface"; import BigNumber from "bignumber.js"; /** @@ -17,8 +17,8 @@ export class ProtoSerializer { * Serializes a Transaction object to a Buffer. Handles low-level conversion logic and field-mappings as well. */ serializeTransaction(transaction: Transaction): Buffer { - let receiverPubkey = new Address(transaction.getReceiver().bech32()).pubkey(); - let senderPubkey = new Address(transaction.getSender().bech32()).pubkey(); + const receiverPubkey = new Address(transaction.getReceiver().bech32()).pubkey(); + const senderPubkey = new Address(transaction.getSender().bech32()).pubkey(); let protoTransaction = new proto.Transaction({ // elrond-go's serializer handles nonce == 0 differently, thus we treat 0 as "undefined". @@ -36,15 +36,30 @@ export class ProtoSerializer { Signature: Buffer.from(transaction.getSignature().hex(), "hex") }); - if ( transaction.getOptions().valueOf() !== TRANSACTION_OPTIONS_DEFAULT ) { + if (transaction.getOptions().valueOf() !== TRANSACTION_OPTIONS_DEFAULT) { protoTransaction.Options = transaction.getOptions().valueOf(); } - - let encoded = proto.Transaction.encode(protoTransaction).finish(); - let buffer = Buffer.from(encoded); + + if (transaction.getOptions().isGuardedTx()) { + const guardianAddress = this.tryGetGuardianAddress(transaction.getGuardian()); + protoTransaction.GuardAddr = new Address(guardianAddress.bech32()).pubkey(); + protoTransaction.GuardSignature = Buffer.from(transaction.getGuardianSignature().hex(), "hex"); + } + + const encoded = proto.Transaction.encode(protoTransaction).finish(); + const buffer = Buffer.from(encoded); + return buffer; } + private tryGetGuardianAddress(guardianAddress: IAddress | undefined): IAddress { + if (guardianAddress) { + return guardianAddress + } + + throw new errors.ErrUnsupportedOperation("getGuardianAddress"); + } + /** * Custom serialization, compatible with elrond-go. */ diff --git a/src/proto/transaction.proto b/src/proto/transaction.proto index dd460c5d..733cb06b 100644 --- a/src/proto/transaction.proto +++ b/src/proto/transaction.proto @@ -15,18 +15,20 @@ import "github.com/gogo/protobuf/gogoproto/gogo.proto"; // Transaction holds all the data needed for a value transfer or SC call message Transaction { - uint64 Nonce = 1 [(gogoproto.jsontag) = "nonce"]; - bytes Value = 2 [(gogoproto.jsontag) = "value", (gogoproto.casttypewith) = "math/big.Int;github.com/ElrondNetwork/elrond-go/data.BigIntCaster"]; - bytes RcvAddr = 3 [(gogoproto.jsontag) = "receiver"]; - bytes RcvUserName = 4 [(gogoproto.jsontag) = "rcvUserName,omitempty"]; - bytes SndAddr = 5 [(gogoproto.jsontag) = "sender"]; - bytes SndUserName = 6 [(gogoproto.jsontag) = "sndUserName,omitempty"]; - uint64 GasPrice = 7 [(gogoproto.jsontag) = "gasPrice,omitempty"]; - uint64 GasLimit = 8 [(gogoproto.jsontag) = "gasLimit,omitempty"]; - bytes Data = 9 [(gogoproto.jsontag) = "data,omitempty"]; - bytes ChainID = 10 [(gogoproto.jsontag) = "chainID"]; - uint32 Version = 11 [(gogoproto.jsontag) = "version"]; - bytes Signature = 12 [(gogoproto.jsontag) = "signature,omitempty"]; - uint32 Options = 13 [(gogoproto.jsontag) = "options,omitempty"]; + uint64 Nonce = 1 [(gogoproto.jsontag) = "nonce"]; + bytes Value = 2 [(gogoproto.jsontag) = "value", (gogoproto.casttypewith) = "math/big.Int;github.com/ElrondNetwork/elrond-go/data.BigIntCaster"]; + bytes RcvAddr = 3 [(gogoproto.jsontag) = "receiver"]; + bytes RcvUserName = 4 [(gogoproto.jsontag) = "rcvUserName,omitempty"]; + bytes SndAddr = 5 [(gogoproto.jsontag) = "sender"]; + bytes SndUserName = 6 [(gogoproto.jsontag) = "sndUserName,omitempty"]; + uint64 GasPrice = 7 [(gogoproto.jsontag) = "gasPrice,omitempty"]; + uint64 GasLimit = 8 [(gogoproto.jsontag) = "gasLimit,omitempty"]; + bytes Data = 9 [(gogoproto.jsontag) = "data,omitempty"]; + bytes ChainID = 10 [(gogoproto.jsontag) = "chainID"]; + uint32 Version = 11 [(gogoproto.jsontag) = "version"]; + bytes Signature = 12 [(gogoproto.jsontag) = "signature,omitempty"]; + uint32 Options = 13 [(gogoproto.jsontag) = "options,omitempty"]; + bytes GuardAddr = 14 [(gogoproto.jsontag) = "guardian,omitempty"]; + bytes GuardSignature = 15 [(gogoproto.jsontag) = "guardianSignature,omitempty"]; } diff --git a/src/smartcontracts/interaction.ts b/src/smartcontracts/interaction.ts index 5886c709..330ce2c8 100644 --- a/src/smartcontracts/interaction.ts +++ b/src/smartcontracts/interaction.ts @@ -36,6 +36,7 @@ export class Interaction { private chainID: IChainID = ""; private querent: IAddress = new Address(); private explicitReceiver?: IAddress; + private sender: IAddress = new Address(); private isWithSingleESDTTransfer: boolean = false; private isWithSingleESDTNFTTransfer: boolean = false; @@ -53,7 +54,7 @@ export class Interaction { this.args = args; this.tokenTransfers = new TokenTransfersWithinInteraction([], this); } - + getContractAddress(): IAddress { return this.contract.getAddress(); } @@ -119,6 +120,8 @@ export class Interaction { }); transaction.setNonce(this.nonce); + transaction.setSender(this.sender); + return transaction; } @@ -173,7 +176,7 @@ export class Interaction { return this; } - useThenIncrementNonceOf(account: Account) : Interaction { + useThenIncrementNonceOf(account: Account): Interaction { return this.withNonce(account.getNonceThenIncrement()); } @@ -182,6 +185,11 @@ export class Interaction { return this; } + withSender(sender: IAddress): Interaction { + this.sender = sender; + return this; + } + /** * Sets the "caller" field on contract queries. */ diff --git a/src/transaction.spec.ts b/src/transaction.spec.ts index 4b1fad9b..e1e14452 100644 --- a/src/transaction.spec.ts +++ b/src/transaction.spec.ts @@ -134,7 +134,26 @@ describe("test transaction construction", async () => { assert.equal("b56769014f2bdc5cf9fc4a05356807d71fcf8775c819b0f1b0964625b679c918ffa64862313bfef86f99b38cb84fcdb16fa33ad6eb565276616723405cd8f109", transaction.getSignature().hex()); assert.equal(transaction.getHash().toString(), "eb30c50c8831885ebcfac986d27e949ec02cf25676e22a009b7a486e5431ec2e"); - let result = transaction.serializeForSigning(wallets.alice.address); + let result = transaction.serializeForSigning(); + assert.isFalse(result.toString().includes("options")); + }); + + it("with guardian field, should be omitted", async () => { + let transaction = new Transaction({ + nonce: 89, + value: 0, + sender: wallets.alice.address, + receiver: wallets.bob.address, + gasPrice: minGasPrice, + gasLimit: minGasLimit, + chainID: "local-testnet" + }); + + await wallets.alice.signer.sign(transaction); + assert.equal("b56769014f2bdc5cf9fc4a05356807d71fcf8775c819b0f1b0964625b679c918ffa64862313bfef86f99b38cb84fcdb16fa33ad6eb565276616723405cd8f109", transaction.getSignature().hex()); + assert.equal(transaction.getHash().toString(), "eb30c50c8831885ebcfac986d27e949ec02cf25676e22a009b7a486e5431ec2e"); + + let result = transaction.serializeForSigning(); assert.isFalse(result.toString().includes("options")); }); @@ -196,7 +215,7 @@ describe("test transaction construction", async () => { chainID: "local-testnet" }); - const plainObject = transaction.toPlainObject(sender); + const plainObject = transaction.toPlainObject(); const restoredTransaction = Transaction.fromPlainObject(plainObject); assert.deepEqual(restoredTransaction, transaction); }); diff --git a/src/transaction.ts b/src/transaction.ts index 89312061..82cacbb0 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -12,7 +12,7 @@ import * as errors from "./errors"; import { ProtoSerializer } from "./proto"; import { Hash } from "./hash"; import { INetworkConfig } from "./interfaceOfNetwork"; -import { TRANSACTION_MIN_GAS_PRICE } from "./constants"; +import { TRANSACTION_MIN_GAS_PRICE, TRANSACTION_OPTIONS_TX_GUARDED, TRANSACTION_OPTIONS_TX_GUARDED_MASK } from "./constants"; const createTransactionHasher = require("blake2b"); const TRANSACTION_HASH_LENGTH = 32; @@ -71,11 +71,21 @@ export class Transaction { */ options: TransactionOptions; + /** + * The address of the guardian. + */ + private guardian?: IAddress; + /** * The signature. */ private signature: ISignature; + /** + * The signature of the guardian. + */ + private guardianSignature: ISignature; + /** * The transaction hash, also used as a transaction identifier. */ @@ -95,6 +105,7 @@ export class Transaction { chainID, version, options, + guardian, }: { nonce?: INonce; value?: ITransactionValue; @@ -106,6 +117,7 @@ export class Transaction { chainID: IChainID; version?: TransactionVersion; options?: TransactionOptions; + guardian?: IAddress; }) { this.nonce = nonce || 0; this.value = value || 0; @@ -117,8 +129,10 @@ export class Transaction { this.chainID = chainID; this.version = version || TransactionVersion.withDefaultVersion(); this.options = options || TransactionOptions.withDefaultOptions(); + this.guardian = guardian || Address.empty(); this.signature = Signature.empty(); + this.guardianSignature = Signature.empty(); this.hash = TransactionHash.empty(); } @@ -149,6 +163,10 @@ export class Transaction { return this.receiver; } + getGuardian(): IAddress | undefined { + return this.guardian; + } + getGasPrice(): IGasPrice { return this.gasPrice; } @@ -189,6 +207,14 @@ export class Transaction { return this.signature; } + getGuardianSignature(): ISignature { + return this.guardianSignature; + } + + setSender(sender: IAddress) { + this.sender = sender; + } + getHash(): TransactionHash { guardNotEmpty(this.hash, "hash"); return this.hash; @@ -200,13 +226,22 @@ export class Transaction { * * @param signedBy The address of the future signer */ - serializeForSigning(signedBy: IAddress): Buffer { + serializeForSigning(): Buffer { // TODO: for appropriate tx.version, interpret tx.options accordingly and sign using the content / data hash - let plain = this.toPlainObject(signedBy); + let plain = this.toPlainObject(); // Make sure we never sign the transaction with another signature set up (useful when using the same method for verification) if (plain.signature) { delete plain.signature; } + + if (plain.guardianSignature) { + delete plain.guardianSignature; + } + + if ((plain.guardian == undefined) || (plain.guardian == "")) { + delete plain.guardian + } + let serialized = JSON.stringify(plain); return Buffer.from(serialized); @@ -218,19 +253,21 @@ export class Transaction { * * @param sender The address of the sender (will be provided when called within the signing procedure) */ - toPlainObject(sender?: IAddress): IPlainTransactionObject { + toPlainObject(): IPlainTransactionObject { return { nonce: this.nonce.valueOf(), value: this.value.toString(), receiver: this.receiver.bech32(), - sender: sender ? sender.bech32() : this.sender.bech32(), + sender: this.sender.bech32(), gasPrice: this.gasPrice.valueOf(), gasLimit: this.gasLimit.valueOf(), data: this.data.length() == 0 ? undefined : this.data.encoded(), chainID: this.chainID.valueOf(), version: this.version.valueOf(), options: this.options.valueOf() == 0 ? undefined : this.options.valueOf(), + guardian: this.guardian?.bech32() ? (this.guardian.bech32() == "" ? undefined : this.guardian.bech32()) : undefined, signature: this.signature.hex() ? this.signature.hex() : undefined, + guardianSignature: this.guardianSignature.hex() ? this.guardianSignature.hex() : undefined, }; } @@ -245,16 +282,23 @@ export class Transaction { value: new BigNumber(plainObjectTransaction.value), receiver: Address.fromString(plainObjectTransaction.receiver), sender: Address.fromString(plainObjectTransaction.sender), + guardian: Address.fromString(plainObjectTransaction.guardian || ""), gasPrice: Number(plainObjectTransaction.gasPrice), gasLimit: Number(plainObjectTransaction.gasLimit), data: new TransactionPayload(Buffer.from(plainObjectTransaction.data || "", "base64")), chainID: String(plainObjectTransaction.chainID), version: new TransactionVersion(plainObjectTransaction.version), }); + if (plainObjectTransaction.signature) { tx.applySignature( new Signature(plainObjectTransaction.signature), - Address.fromString(plainObjectTransaction.sender) + ); + } + + if (plainObjectTransaction.guardianSignature) { + tx.applyGuardianSignature( + new Signature(plainObjectTransaction.guardianSignature) ); } @@ -267,9 +311,18 @@ export class Transaction { * @param signature The signature, as computed by a signer. * @param signedBy The address of the signer. */ - applySignature(signature: ISignature, signedBy: IAddress) { + applySignature(signature: ISignature) { this.signature = signature; - this.sender = signedBy; + this.hash = TransactionHash.compute(this); + } + + /** + * Applies the guardian signature on the transaction. + * + * @param guardianSignature The signature, as computed by a signer. + */ + applyGuardianSignature(guardianSignature: ISignature) { + this.guardianSignature = guardianSignature; this.hash = TransactionHash.compute(this); }