Skip to content

Commit

Permalink
feat: Refactor SignatureMap for Multi-Transaction Support in _signedT…
Browse files Browse the repository at this point in the history
…ransactions (#2601)

* feat: init commit for signing of tx changes

Signed-off-by: Ivaylo Nikolov <[email protected]>

* wip: working stage for tx tool

Signed-off-by: Ivaylo Nikolov <[email protected]>

* fix: circular dependancy

Signed-off-by: Ivaylo Nikolov <[email protected]>

* refactor: remove repeating check

Signed-off-by: Ivaylo Nikolov <[email protected]>

* refactor: add error

Signed-off-by: Ivaylo Nikolov <[email protected]>

* style: lines

Signed-off-by: Ivaylo Nikolov <[email protected]>

* feat: add flattener for signatures

Signed-off-by: Ivaylo Nikolov <[email protected]>

* refacotr: sigmap exports and returns

Signed-off-by: Ivaylo Nikolov <[email protected]>

* refactor: sigpairmap exports and returns

Signed-off-by: Ivaylo Nikolov <[email protected]>

* fix: node account id sig map didnt work with empty tx ids

Signed-off-by: Ivaylo Nikolov <[email protected]>

* fix: freeze if not frozen

Signed-off-by: Ivaylo Nikolov <[email protected]>

* test: fix tests

Signed-off-by: Ivaylo Nikolov <[email protected]>

* feat: export SignatureMap

Signed-off-by: Svetoslav Borislavov <[email protected]>

* style: remove empty lines

Signed-off-by: Ivaylo Nikolov <[email protected]>

* style: equals instead of equal

Signed-off-by: Ivaylo Nikolov <[email protected]>

* fix: incomplete file append with multiple node account ids

Signed-off-by: Svetoslav Borislavov <[email protected]>

* fix: max chunks from protobuf

Signed-off-by: Svetoslav Borislavov <[email protected]>

* refactor: code comments and jsdocs

Signed-off-by: Ivaylo Nikolov <[email protected]>

* refactor: extract to 3 constants

Signed-off-by: Ivaylo Nikolov <[email protected]>

* test: check if sigs are passed correctly

Signed-off-by: Ivaylo Nikolov <[email protected]>

* refactor: extract variables to const

Signed-off-by: Ivaylo Nikolov <[email protected]>

---------

Signed-off-by: Ivaylo Nikolov <[email protected]>
Signed-off-by: Svetoslav Borislavov <[email protected]>
Co-authored-by: Svetoslav Borislavov <[email protected]>
  • Loading branch information
ivaylonikolov7 and SvetBorislavov authored Oct 29, 2024
1 parent 5d11526 commit 0d36abe
Show file tree
Hide file tree
Showing 14 changed files with 525 additions and 454 deletions.
47 changes: 29 additions & 18 deletions src/PrivateKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ import Mnemonic from "./Mnemonic.js";
import PublicKey from "./PublicKey.js";
import Key from "./Key.js";
import CACHE from "./Cache.js";
import SignatureMap from "./transaction/SignatureMap.js";

import AccountId from "./account/AccountId.js";
import TransactionId from "./transaction/TransactionId.js";
import { proto } from "@hashgraph/proto";

/**
* @typedef {import("./transaction/Transaction.js").default} Transaction
* @typedef {import("./account/AccountId.js").default} AccountId
*/

/**
Expand All @@ -35,6 +39,7 @@ import CACHE from "./Cache.js";
* @typedef {import("@hashgraph/proto").proto.ITransaction} HashgraphProto.proto.ITransaction
* @typedef {import("@hashgraph/proto").proto.ISignaturePair} HashgraphProto.proto.ISignaturePair
* @typedef {import("@hashgraph/proto").proto.ISignedTransaction} HashgraphProto.proto.ISignedTransaction
* @typedef {import("@hashgraph/proto").proto.TransactionBody} HashgraphProto.proto.TransactionBody
*/

export default class PrivateKey extends Key {
Expand Down Expand Up @@ -325,27 +330,33 @@ export default class PrivateKey extends Key {

/**
* @param {Transaction} transaction
* @returns {Uint8Array | Uint8Array[]}
* @returns {SignatureMap}
*/
signTransaction(transaction) {
const signatures = transaction._signedTransactions.list.map(
(signedTransaction) => {
const bodyBytes = signedTransaction.bodyBytes;

if (!bodyBytes) {
return new Uint8Array();
}

return this._key.sign(bodyBytes);
},
);

transaction.addSignature(this.publicKey, signatures);
const sigMap = new SignatureMap();

for (const signedTx of transaction._signedTransactions.list) {
const bodyBytes = signedTx.bodyBytes;
if (!bodyBytes) throw new Error("Body bytes are missing");

const body = proto.TransactionBody.decode(bodyBytes);
if (!body.transactionID || !body.nodeAccountID) {
throw new Error(
"Transaction ID or Node Account ID not found in the signed transaction",
);
}

const nodeId = AccountId._fromProtobuf(body.nodeAccountID);
const transactionId = TransactionId._fromProtobuf(
body.transactionID,
);
const sig = this._key.sign(bodyBytes);
sigMap.addSignature(nodeId, transactionId, this.publicKey, sig);
}

// Return directly Uint8Array if there is only one signature
return signatures.length === 1 ? signatures[0] : signatures;
transaction.addSignature(this.publicKey, sigMap);
return sigMap;
}

/**
* Check if `derive` can be called on this private key.
*
Expand Down
1 change: 1 addition & 0 deletions src/exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export { default as ScheduleInfo } from "./schedule/ScheduleInfo.js";
export { default as ScheduleInfoQuery } from "./schedule/ScheduleInfoQuery.js";
export { default as ScheduleSignTransaction } from "./schedule/ScheduleSignTransaction.js";
export { default as SemanticVersion } from "./network/SemanticVersion.js";
export { default as SignatureMap } from "./transaction/SignatureMap.js";
export { default as Signer } from "./Signer.js";
export { default as SignerSignature } from "./SignerSignature.js";
export { default as Status } from "./Status.js";
Expand Down
7 changes: 4 additions & 3 deletions src/file/FileAppendTransaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,10 @@ export default class FileAppendTransaction extends Transaction {
);
contents = concat;
}

const chunkSize = append.contents?.length || undefined;
const maxChunks = bodies.length || undefined;
const maxChunks = bodies.length
? bodies.length / incrementValue
: undefined;
let chunkInterval;
if (transactionIds.length > 1) {
const firstValidStart = transactionIds[0].validStart;
Expand Down Expand Up @@ -536,7 +537,7 @@ export default class FileAppendTransaction extends Transaction {
this._transactions.push(this._makeSignedTransaction(null));
} else {
for (const nodeAccountId of this._nodeAccountIds.list) {
this._signedTransactions.push(
this._transactions.push(
this._makeSignedTransaction(nodeAccountId),
);
}
Expand Down
52 changes: 37 additions & 15 deletions src/transaction/NodeAccountIdSignatureMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,63 @@
*/

import ObjectMap from "../ObjectMap.js";
import PublicKey from "../PublicKey.js";
import TransactionId from "./TransactionId.js";
import SignaturePairMap from "./SignaturePairMap.js";
import * as HashgraphProto from "@hashgraph/proto";

/**
* @augments {ObjectMap<PublicKey, Uint8Array>}
* @augments {ObjectMap<TransactionId, SignaturePairMap>}
*/
export default class NodeAccountIdSignatureMap extends ObjectMap {
constructor() {
super((s) => PublicKey.fromString(s));
super((s) => TransactionId.fromString(s));
}

/**
* @param {import("@hashgraph/proto").proto.ISignatureMap} sigMap
* This function is used to create a NodeAccountIdSignaturemap from an already built transaction.
* @param { import('./List.js').default<import("@hashgraph/proto").proto.ISignedTransaction>} signedTransactions
* @returns {NodeAccountIdSignatureMap}
*/
static _fromTransactionSigMap(sigMap) {
static _fromSignedTransactions(signedTransactions) {
const signatures = new NodeAccountIdSignatureMap();

const sigPairs = sigMap.sigPair != null ? sigMap.sigPair : [];
for (const { bodyBytes, sigMap } of signedTransactions.list) {
if (bodyBytes != null && sigMap != null) {
const body =
HashgraphProto.proto.TransactionBody.decode(bodyBytes);

for (const sigPair of sigPairs) {
if (sigPair.pubKeyPrefix != null) {
if (sigPair.ed25519 != null) {
signatures._set(
PublicKey.fromBytesED25519(sigPair.pubKeyPrefix),
sigPair.ed25519,
if (body.transactionID != null) {
const transactionId = TransactionId._fromProtobuf(
body.transactionID,
);
} else if (sigPair.ECDSASecp256k1 != null) {

signatures._set(
PublicKey.fromBytesECDSA(sigPair.pubKeyPrefix),
sigPair.ECDSASecp256k1,
transactionId,
SignaturePairMap._fromTransactionSigMap(sigMap),
);
}
}
}

return signatures;
}

/**
*
* Adds a signature pair for this transaction id.
* @param {TransactionId} txId
* @param {import("../SignerSignature.js").PublicKey} publicKey
* @param {Uint8Array} signature
*/
addSignature(txId, publicKey, signature) {
const sigPairMap = this.get(txId);
if (sigPairMap) {
sigPairMap.addSignature(publicKey, signature);
} else {
this._set(
txId,
new SignaturePairMap().addSignature(publicKey, signature),
);
}
}
}
73 changes: 67 additions & 6 deletions src/transaction/SignatureMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,94 @@
import NodeAccountIdSignatureMap from "./NodeAccountIdSignatureMap.js";
import ObjectMap from "../ObjectMap.js";
import AccountId from "../account/AccountId.js";
import List from "./List.js";

/**
* @augments {ObjectMap<AccountId, NodeAccountIdSignatureMap>}
*/
export default class SignatureMap extends ObjectMap {
/**
* @typedef {import("../transaction/TransactionId.js").default} TransactionId
* @typedef {import("../transaction/SignaturePairMap.js").default} SignaturePairMap
*/
constructor() {
super((s) => AccountId.fromString(s));
}

/**
* This function is used to create a SignatureMap from an already built transaction.
* @param {import("./Transaction.js").default} transaction
* @returns {SignatureMap}
*/
static _fromTransaction(transaction) {
const signatures = new SignatureMap();

for (let i = 0; i < transaction._nodeAccountIds.length; i++) {
const sigMap = transaction._signedTransactions.get(i).sigMap;
const rowLength = transaction._nodeAccountIds.length;
const columns = transaction._signedTransactions.length / rowLength;

/*
this setup implies that the signed transactions are stored sequentially
in the signed transactions list. This means that the first rowLength
signed transactions are for the first node account id, the next rowLength
signed transactions are for the second node account id and so on.
*/
for (let row = 0; row < rowLength; row++) {
/** @type { List<import("@hashgraph/proto").proto.ISignedTransaction> } */
const signedTransactions = new List();

if (sigMap != null) {
signatures._set(
transaction._nodeAccountIds.list[i],
NodeAccountIdSignatureMap._fromTransactionSigMap(sigMap),
for (let col = 0; col < columns; col++) {
signedTransactions.push(
transaction._signedTransactions.get(col * rowLength + row),
);
}

signatures._set(
transaction._nodeAccountIds.list[row],
NodeAccountIdSignatureMap._fromSignedTransactions(
signedTransactions,
),
);
}

return signatures;
}

/**
* Updates the signature map with the given signature.
* by generating a new node account id signature map if it does not exist
* or adding the signature to the existing node account id signature map.
*
* @param {AccountId} nodeId
* @param {TransactionId} txId
* @param {import("../SignerSignature.js").PublicKey} publicKey
* @param {Uint8Array} signature
* @returns {SignatureMap}
*/
addSignature(nodeId, txId, publicKey, signature) {
let nodeAccountIdSigdMap = this.get(nodeId);

if (!nodeAccountIdSigdMap) {
nodeAccountIdSigdMap = new NodeAccountIdSignatureMap();
this._set(nodeId, nodeAccountIdSigdMap);
}

nodeAccountIdSigdMap.addSignature(txId, publicKey, signature);
this._set(nodeId, nodeAccountIdSigdMap);

return this;
}
/**
* @returns {SignaturePairMap[]}
*/
getFlatSignatureList() {
const flatSignatureList = [];

for (const nodeAccountIdSignatureMap of this.values()) {
for (const tx of nodeAccountIdSignatureMap.values()) {
flatSignatureList.push(tx);
}
}

return flatSignatureList;
}
}
53 changes: 53 additions & 0 deletions src/transaction/SignaturePairMap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import ObjectMap from "../ObjectMap.js";
import PublicKey from "../PublicKey.js";

/**
* @augments {ObjectMap<PublicKey, Uint8Array>}
*/
export default class SignaturePairMap extends ObjectMap {
constructor() {
super((s) => PublicKey.fromString(s));
}

/**
* This function is used to create a SignaturePairMap from an already built transaction.
* @param {import("@hashgraph/proto").proto.ISignatureMap} sigMap
* @returns {SignaturePairMap}
*/
static _fromTransactionSigMap(sigMap) {
const signatures = new SignaturePairMap();

const sigPairs = sigMap.sigPair != null ? sigMap.sigPair : [];

for (const sigPair of sigPairs) {
if (sigPair.pubKeyPrefix == null) {
continue;
}

if (sigPair.ed25519 != null) {
signatures._set(
PublicKey.fromBytesED25519(sigPair.pubKeyPrefix),
sigPair.ed25519,
);
} else if (sigPair.ECDSASecp256k1 != null) {
signatures._set(
PublicKey.fromBytesECDSA(sigPair.pubKeyPrefix),
sigPair.ECDSASecp256k1,
);
}
}

return signatures;
}

/**
*
* @param {PublicKey} pubKey
* @param {Uint8Array} signature
* @returns {SignaturePairMap}
*/
addSignature(pubKey, signature) {
this._set(pubKey, signature);
return this;
}
}
Loading

0 comments on commit 0d36abe

Please sign in to comment.