-
Notifications
You must be signed in to change notification settings - Fork 148
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add multi-signature and multi-node support for signing and addi… (
#2514) * feat: Add multi-signature and multi-node support for signing and adding signatures Signed-off-by: ivaylogarnev-limechain <[email protected]> * test: Added signTransaction method tests Signed-off-by: ivaylogarnev-limechain <[email protected]> * test: Added some integration tests for signTransaction method Signed-off-by: ivaylogarnev-limechain <[email protected]> --------- Signed-off-by: ivaylogarnev-limechain <[email protected]>
- Loading branch information
1 parent
ba47014
commit cd14715
Showing
5 changed files
with
443 additions
and
27 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
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 |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { | ||
PrivateKey, | ||
AccountCreateTransaction, | ||
Hbar, | ||
AccountId, | ||
KeyList, | ||
TransferTransaction, | ||
Transaction, | ||
Status, | ||
FileAppendTransaction, | ||
FileCreateTransaction, | ||
} from "../../src/exports.js"; | ||
import dotenv from "dotenv"; | ||
import IntegrationTestEnv from "./client/NodeIntegrationTestEnv.js"; | ||
|
||
import { expect } from "chai"; | ||
|
||
dotenv.config(); | ||
|
||
describe("PrivateKey signTransaction", function () { | ||
let env, user1Key, user2Key, createdAccountId, keyList; | ||
|
||
// Setting up the environment and creating a new account with a key list | ||
before(async () => { | ||
env = await IntegrationTestEnv.new(); | ||
|
||
user1Key = PrivateKey.generate(); | ||
user2Key = PrivateKey.generate(); | ||
keyList = new KeyList([user1Key.publicKey, user2Key.publicKey]); | ||
|
||
// Create account | ||
const createAccountTransaction = new AccountCreateTransaction() | ||
.setInitialBalance(new Hbar(2)) | ||
.setKey(keyList); | ||
|
||
const createResponse = await createAccountTransaction.execute( | ||
env.client, | ||
); | ||
const createReceipt = await createResponse.getReceipt(env.client); | ||
|
||
createdAccountId = createReceipt.accountId; | ||
|
||
expect(createdAccountId).to.exist; | ||
}); | ||
|
||
it("Transfer Transaction Execution with Multiple Nodes", async () => { | ||
// Create and sign transfer transaction | ||
const transferTransaction = new TransferTransaction() | ||
.addHbarTransfer(createdAccountId, new Hbar(-1)) | ||
.addHbarTransfer("0.0.3", new Hbar(1)) | ||
.setNodeAccountIds([ | ||
new AccountId(3), | ||
new AccountId(4), | ||
new AccountId(5), | ||
]) | ||
.freezeWith(env.client); | ||
|
||
// Serialize and sign the transaction | ||
const transferTransactionBytes = transferTransaction.toBytes(); | ||
const user1Signatures = user1Key.signTransaction(transferTransaction); | ||
const user2Signatures = user2Key.signTransaction(transferTransaction); | ||
|
||
// Deserialize the transaction and add signatures | ||
const signedTransaction = Transaction.fromBytes( | ||
transferTransactionBytes, | ||
); | ||
signedTransaction.addSignature(user1Key.publicKey, user1Signatures); | ||
signedTransaction.addSignature(user2Key.publicKey, user2Signatures); | ||
|
||
// Execute the signed transaction | ||
const result = await signedTransaction.execute(env.client); | ||
const receipt = await result.getReceipt(env.client); | ||
|
||
expect(receipt.status).to.be.equal(Status.Success); | ||
}); | ||
|
||
it("File Append Transaction Execution with Multiple Nodes", async () => { | ||
const operatorKey = env.operatorKey.publicKey; | ||
|
||
// Create file | ||
let response = await new FileCreateTransaction() | ||
.setKeys([operatorKey]) | ||
.setContents("[e2e::FileCreateTransaction]") | ||
.execute(env.client); | ||
|
||
let createTxReceipt = await response.getReceipt(env.client); | ||
const file = createTxReceipt.fileId; | ||
|
||
// Append content to the file | ||
const fileAppendTx = new FileAppendTransaction() | ||
.setFileId(file) | ||
.setContents("[e2e::FileAppendTransaction]") | ||
.setNodeAccountIds([ | ||
new AccountId(3), | ||
new AccountId(4), | ||
new AccountId(5), | ||
]) | ||
.freezeWith(env.client); | ||
|
||
// Serialize and sign the transaction | ||
const fileAppendTransactionBytes = fileAppendTx.toBytes(); | ||
const user1Signatures = user1Key.signTransaction(fileAppendTx); | ||
const user2Signatures = user2Key.signTransaction(fileAppendTx); | ||
|
||
// Deserialize the transaction and add signatures | ||
const signedTransaction = Transaction.fromBytes( | ||
fileAppendTransactionBytes, | ||
); | ||
signedTransaction.addSignature(user1Key.publicKey, user1Signatures); | ||
signedTransaction.addSignature(user2Key.publicKey, user2Signatures); | ||
|
||
// Execute the signed transaction | ||
const result = await signedTransaction.execute(env.client); | ||
const receipt = await result.getReceipt(env.client); | ||
|
||
expect(receipt.status).to.be.equal(Status.Success); | ||
}); | ||
}); |
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 |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { expect } from "chai"; | ||
import sinon from "sinon"; | ||
|
||
import { PrivateKey } from "../../src/index.js"; | ||
import Transaction from "../../src/transaction/Transaction.js"; | ||
|
||
describe("PrivateKey signTransaction", function () { | ||
let privateKey, mockedTransaction, mockedSignature; | ||
|
||
beforeEach(() => { | ||
privateKey = PrivateKey.generate(); | ||
|
||
mockedTransaction = sinon.createStubInstance(Transaction); | ||
mockedSignature = new Uint8Array([4, 5, 6]); | ||
|
||
// Mock addSignature method on the transaction | ||
mockedTransaction.addSignature = sinon.spy(); | ||
}); | ||
|
||
it("should sign transaction and add signature", function () { | ||
// Mock _signedTransactions.list to return an array with one signed transaction | ||
mockedTransaction._signedTransactions = { | ||
list: [{ bodyBytes: new Uint8Array([1, 2, 3]) }], | ||
}; | ||
|
||
// Stub the _key.sign method to return a mock signature | ||
privateKey._key = { | ||
sign: sinon.stub().returns(mockedSignature), | ||
}; | ||
|
||
// Call the real signTransaction method | ||
const signatures = privateKey.signTransaction(mockedTransaction); | ||
|
||
// Validate that the signatures are correct | ||
expect(signatures).to.deep.equal([mockedSignature]); | ||
|
||
sinon.assert.calledWith( | ||
mockedTransaction.addSignature, | ||
privateKey.publicKey, | ||
[mockedSignature], | ||
); | ||
|
||
// Ensure that sign method of the privateKey._key was called | ||
sinon.assert.calledOnce(privateKey._key.sign); | ||
}); | ||
|
||
it("should return empty signature if bodyBytes are missing", function () { | ||
// Set bodyBytes to null to simulate missing bodyBytes | ||
mockedTransaction._signedTransactions = { | ||
list: [{ bodyBytes: null }], | ||
}; | ||
|
||
// Stub the _key.sign method to return a mock signature | ||
privateKey._key = { | ||
sign: sinon.stub().returns(mockedSignature), | ||
}; | ||
|
||
// Call signTransaction method | ||
const signatures = privateKey.signTransaction(mockedTransaction); | ||
|
||
// Validate that an empty Uint8Array was returned | ||
expect(signatures).to.deep.equal([new Uint8Array()]); | ||
|
||
// Ensure that the transaction's addSignature method was called with the empty signature | ||
sinon.assert.calledWith( | ||
mockedTransaction.addSignature, | ||
privateKey.publicKey, | ||
[new Uint8Array()], | ||
); | ||
|
||
// Ensure that sign method of the privateKey._key was not called | ||
sinon.assert.notCalled(privateKey._key.sign); | ||
}); | ||
|
||
it("should sign transaction and add multiple signature", function () { | ||
const mockedSignatures = [ | ||
new Uint8Array([10, 11, 12]), | ||
new Uint8Array([13, 14, 15]), | ||
new Uint8Array([16, 17, 18]), | ||
]; | ||
|
||
const signedTransactions = [ | ||
{ bodyBytes: new Uint8Array([1, 2, 3]) }, | ||
{ bodyBytes: new Uint8Array([4, 5, 6]) }, | ||
{ bodyBytes: new Uint8Array([7, 8, 9]) }, | ||
]; | ||
|
||
// Mock _signedTransactions.list to return an array of transaction | ||
mockedTransaction._signedTransactions = { | ||
list: signedTransactions, | ||
}; | ||
|
||
// Stub the _key.sign method to return a list of mock signature | ||
privateKey._key = { | ||
sign: sinon | ||
.stub() | ||
.onCall(0) | ||
.returns(mockedSignatures[0]) | ||
.onCall(1) | ||
.returns(mockedSignatures[1]) | ||
.onCall(2) | ||
.returns(mockedSignatures[2]), | ||
}; | ||
|
||
// Call the real signTransaction method | ||
const signatures = privateKey.signTransaction(mockedTransaction); | ||
|
||
// Validate that the signatures are correct | ||
expect(signatures).to.deep.equal(mockedSignatures); | ||
|
||
// Ensure that the transaction's addSignature method was called with the correct arguments | ||
sinon.assert.calledWith( | ||
mockedTransaction.addSignature, | ||
privateKey.publicKey, | ||
mockedSignatures, | ||
); | ||
|
||
// Ensure that sign method of the privateKey._key was called the correct number of times | ||
sinon.assert.callCount(privateKey._key.sign, 3); | ||
}); | ||
}); |
Oops, something went wrong.