Skip to content

Commit de37d4e

Browse files
chore: return Privatekey, Transaction and PublicKey legacy functionality (#2943)
* revert: return legacy classes for backwards compatibility Signed-off-by: Ivaylo Nikolov <[email protected]> * test: return legacy unit tests Signed-off-by: Ivaylo Nikolov <[email protected]> * fix: remove signature map legacy augments Signed-off-by: Ivaylo Nikolov <[email protected]> * fix: remove additional parameter to addsignature Signed-off-by: Ivaylo Nikolov <[email protected]> * chore: deprecate functions Signed-off-by: Ivaylo Nikolov <[email protected]> * feat: add legacy examples Signed-off-by: Ivaylo Nikolov <[email protected]> * chore: update jsdoc types Signed-off-by: Ivaylo Nikolov <[email protected]> * fix: this shouldnt be run on testnet Signed-off-by: Ivaylo Nikolov <[email protected]> * chore: add description for the mix of signning transactions Signed-off-by: Ivaylo Nikolov <[email protected]> * docs: move docs Signed-off-by: Ivaylo Nikolov <[email protected]> * docs: add migrating doc for sign transaction Signed-off-by: Ivaylo Nikolov <[email protected]> * chore: add documentation message Signed-off-by: Ivaylo Nikolov <[email protected]> * chore: add comment to example Signed-off-by: Ivaylo Nikolov <[email protected]> * docs: update docs wording Signed-off-by: Ivaylo Nikolov <[email protected]> * chore: move from example to integration Signed-off-by: Ivaylo Nikolov <[email protected]> * chore: reword variable Signed-off-by: Ivaylo Nikolov <[email protected]> * docs: change overview Signed-off-by: Ivaylo Nikolov <[email protected]> * fix: eslint warnings Signed-off-by: Ivaylo Nikolov <[email protected]> --------- Signed-off-by: Ivaylo Nikolov <[email protected]>
1 parent 195d74f commit de37d4e

14 files changed

+1605
-5
lines changed
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {
2+
PrivateKey,
3+
Client,
4+
AccountId,
5+
AccountCreateTransaction,
6+
} from "@hashgraph/sdk";
7+
8+
import dotenv from "dotenv";
9+
10+
dotenv.config();
11+
12+
const OPERATOR_ID = AccountId.fromString(process.env.OPERATOR_ID);
13+
const OPERATOR_KEY = PrivateKey.fromStringED25519(process.env.OPERATOR_KEY);
14+
const HEDERA_NETWORK = process.env.HEDERA_NETWORK;
15+
16+
async function main() {
17+
// Step 0: Create and configure the SDK Client.
18+
const client = Client.forName(HEDERA_NETWORK);
19+
client.setOperator(OPERATOR_ID, OPERATOR_KEY);
20+
21+
// Step 1: Generate private key for a future account create transaction
22+
const key = PrivateKey.generateED25519();
23+
24+
// Step 2: Create transaction without signing it
25+
const tx = new AccountCreateTransaction()
26+
.setKeyWithoutAlias(key.publicKey)
27+
.freezeWith(client);
28+
29+
// Step 3: Sign transaction using your private key
30+
// eslint-disable-next-line deprecation/deprecation
31+
const signature = key.signTransaction(tx, true);
32+
33+
// Step 4: add the generated signature to transaction
34+
// it will use the old legacy way because of the type of signature
35+
// eslint-disable-next-line deprecation/deprecation
36+
tx.addSignature(key.publicKey, signature);
37+
38+
// Step 5: get the outcome of the transaction
39+
const { status } = await (await tx.execute(client)).getReceipt(client);
40+
console.log("STATUS OF TRANSACTION IS", status.toString());
41+
42+
client.close();
43+
}
44+
45+
void main();
File renamed without changes.

manual/MIGRATING_SIGN_TRANSACTION.md

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Migration Guide: Transaction Signing in Hedera SDK
2+
3+
## Overview
4+
5+
The Hedera SDK has updated its transaction signing mechanism to provide functionaly that can work with multi-node chunked transaction and a more structured approach to managing signatures. This guide will help you migrate from the legacy mode to the new mode.
6+
7+
## Key Changes
8+
9+
### Signing a transaction
10+
11+
- Legacy mode returns raw signatures as `Uint8Array` or `Uint8Array[]` when signing transactions with a private keys
12+
- New mode returns `SignatureMap` when signing transactions with a private key
13+
14+
### Adding a signature
15+
16+
#### Legacy mode:
17+
18+
- Signatures are raw byte arrays (`Uint8Array`)
19+
- No built-in organization of signatures by node or transaction ID
20+
- Simpler but less structured approach
21+
22+
#### New Mode:
23+
24+
- **Supports signing of chunked transactions e.g FileAppend chunk transaction**
25+
- Signatures are organized in a SignatureMap class
26+
- Signatures are mapped to specific node IDs and transaction IDs
27+
- More structured and type-safe approach
28+
29+
## Important Considerations
30+
31+
1. Backward Compatibility - Legacy mode still works but new code should always use the new mode
32+
2. Performance wasn't reduced with these latest changes
33+
3. Error Handling:
34+
35+
- New mode provides better error messages
36+
- Easier to debug signature-related issues
37+
38+
4. Multi-signature Operations:
39+
40+
- Much cleaner handling of multiple signatures
41+
- Better tracking of which keys have signed
42+
43+
5. Users can still use both legacy and non-legacy signing of transactions
44+
45+
## Code examples:
46+
47+
### Signing a transaction
48+
49+
#### Legacy mode:
50+
51+
```
52+
const transaction = new AccountCreateTransaction()
53+
.setKey(newKey.publicKey)
54+
.freezeWith(client);
55+
56+
const signature = privateKey.signTransaction(transaction, true);
57+
```
58+
59+
#### New mode
60+
61+
```
62+
const transaction = new AccountCreateTransaction()
63+
.setKey(newKey.publicKey)
64+
.freezeWith(client);
65+
66+
// Modern signing - returns SignatureMap
67+
const signatureMap = privateKey.signTransaction(transaction); // legacy parameter defaults to false
68+
```
69+
70+
### Adding signatures
71+
72+
#### Legacy mode
73+
74+
```
75+
// Adding signatures directly with Uint8Array
76+
const signatures = privateKey.signTransaction(transaction, true);
77+
transaction.addSignature(privateKey.publicKey, signatures);
78+
```
79+
80+
#### New Mode:
81+
82+
```
83+
// SignatureMap handles the signature organization
84+
const signatureMap = privateKey.signTransaction(transaction);
85+
transaction.addSignature(privateKey.publicKey, signatureMap);
86+
```
87+
88+
### Why and when?
89+
90+
## When did we make this change?
91+
92+
- Current Status: The change is being implemented in response to issue [#2595](https://github.com/hiero-ledger/hiero-sdk-js/issues/2595)
93+
94+
## Implementation Timeline:
95+
96+
- Available now: New signature mode with SignatureMap
97+
- Deprecation: Legacy mode is deprecated and will be removed in v3.0.0
98+
- Migration Period: Users should migrate their code before updating to v1.0.0
99+
100+
## Legacy Signature Mode Deprecation Notice
101+
102+
The legacy transaction signing mode (using signTransaction(transaction, true) and raw Uint8Array signatures) is scheduled for removal in the next major version (v3.0.0) of the SDK. This legacy mode has been deprecated in favor of the new SignatureMap-based signature system, which provides better type safety, improved signature organization, and more robust multi-signature support. Users should migrate their applications to use the new signature mode before updating to v1.0.0. The new mode is already available and can be used by simply removing the legacy parameter from signTransaction() calls and updating signature handling to work with SignatureMap. For migration assistance, please refer to our Migration Guide. After v3.0.0, only the new signature mode will be supported, and applications using the legacy mode will need to be updated to continue functioning.
File renamed without changes.

RELEASE.md manual/RELEASE.md

File renamed without changes.

src/PrivateKey.js

+46-1
Original file line numberDiff line numberDiff line change
@@ -303,10 +303,31 @@ export default class PrivateKey extends Key {
303303
}
304304

305305
/**
306+
* @deprecated - Use legacy=false flag to use the modern approach
307+
* or don't pass it at all.
308+
* @overload
306309
* @param {Transaction} transaction
310+
* @param {true} legacy
311+
* @returns {Uint8Array | Uint8Array[] }
312+
*/
313+
314+
/**
315+
* @overload
316+
* @param {Transaction} transaction
317+
* @param {false} [legacy]
307318
* @returns {SignatureMap}
308319
*/
309-
signTransaction(transaction) {
320+
321+
/**
322+
* @param {Transaction} transaction
323+
* @param {boolean} [legacy]
324+
* @returns {Uint8Array | Uint8Array[] | SignatureMap}
325+
*/
326+
signTransaction(transaction, legacy = false) {
327+
if (legacy) {
328+
return this._signTransactionLegacy(transaction);
329+
}
330+
310331
const sigMap = new SignatureMap();
311332

312333
for (const signedTx of transaction._signedTransactions.list) {
@@ -331,6 +352,30 @@ export default class PrivateKey extends Key {
331352
transaction.addSignature(this.publicKey, sigMap);
332353
return sigMap;
333354
}
355+
356+
/**
357+
* deprecated - This method is deprecated and will be removed in future versions.
358+
* Use the `signTransaction` method with the `legacy=false` flag or don't
359+
* pass it all for the modern approach.
360+
* @param {Transaction} transaction
361+
* @returns {Uint8Array | Uint8Array[]}
362+
*/
363+
_signTransactionLegacy(transaction) {
364+
const signatures = transaction._signedTransactions.list.map(
365+
(signedTransaction) => {
366+
const bodyBytes = signedTransaction.bodyBytes;
367+
if (!bodyBytes) {
368+
return new Uint8Array();
369+
}
370+
371+
return this._key.sign(bodyBytes);
372+
},
373+
);
374+
transaction.addSignature(this.publicKey, signatures);
375+
// Return directly Uint8Array if there is only one signature
376+
return signatures.length === 1 ? signatures[0] : signatures;
377+
}
378+
334379
/**
335380
* Check if `derive` can be called on this private key.
336381
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import ObjectMap from "../ObjectMap.js";
2+
import PublicKey from "../PublicKey.js";
3+
4+
/**
5+
* @deprecated
6+
* @augments {ObjectMap<PublicKey, Uint8Array>}
7+
*/
8+
export default class NodeAccountIdSignatureMap extends ObjectMap {
9+
constructor() {
10+
super((s) => PublicKey.fromString(s));
11+
}
12+
13+
/**
14+
* @param {import("@hashgraph/proto").proto.ISignatureMap} sigMap
15+
* @returns {NodeAccountIdSignatureMap}
16+
*/
17+
static _fromTransactionSigMap(sigMap) {
18+
// eslint-disable-next-line deprecation/deprecation
19+
const signatures = new NodeAccountIdSignatureMap();
20+
const sigPairs = sigMap.sigPair != null ? sigMap.sigPair : [];
21+
22+
for (const sigPair of sigPairs) {
23+
if (sigPair.pubKeyPrefix != null) {
24+
if (sigPair.ed25519 != null) {
25+
signatures._set(
26+
PublicKey.fromBytesED25519(sigPair.pubKeyPrefix),
27+
sigPair.ed25519,
28+
);
29+
} else if (sigPair.ECDSASecp256k1 != null) {
30+
signatures._set(
31+
PublicKey.fromBytesECDSA(sigPair.pubKeyPrefix),
32+
33+
sigPair.ECDSASecp256k1,
34+
);
35+
}
36+
}
37+
}
38+
39+
return signatures;
40+
}
41+
}

src/transaction/SignatureMapLegacy.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/* eslint-disable deprecation/deprecation */
2+
import NodeAccountIdSignatureMapLegacy from "./NodeAccountIdSignatureMapLegacy.js";
3+
import ObjectMap from "../ObjectMap.js";
4+
import AccountId from "../account/AccountId.js";
5+
6+
/**
7+
* @deprecated
8+
* @augments {ObjectMap<AccountId, NodeAccountIdSignatureMapLegacy>}
9+
*/
10+
export default class SignatureMap extends ObjectMap {
11+
constructor() {
12+
super((s) => AccountId.fromString(s));
13+
}
14+
15+
/**
16+
* @param {import("./Transaction.js").default} transaction
17+
* @returns {SignatureMap}
18+
*/
19+
static _fromTransaction(transaction) {
20+
const signatures = new SignatureMap();
21+
22+
for (let i = 0; i < transaction._nodeAccountIds.length; i++) {
23+
const sigMap = transaction._signedTransactions.get(i).sigMap;
24+
25+
if (sigMap != null) {
26+
signatures._set(
27+
transaction._nodeAccountIds.list[i],
28+
NodeAccountIdSignatureMapLegacy._fromTransactionSigMap(
29+
sigMap,
30+
),
31+
);
32+
}
33+
}
34+
35+
return signatures;
36+
}
37+
}

0 commit comments

Comments
 (0)