diff --git a/README.md b/README.md index 36061cc67..ebe641399 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ const transaction = new NativeTransferBuilder() .payment(100_000_000) .build(); -await transaction.sign(privateKey); +transaction.sign(privateKey); try { const result = await rpcClient.putTransaction(transaction); @@ -194,7 +194,7 @@ deployHeader.account = senderKey.publicKey; deployHeader.chainName = 'casper-test'; const deploy = Deploy.makeDeploy(deployHeader, payment, session); -await deploy.sign(senderKey); +deploy.sign(senderKey); const result = await rpcClient.putDeploy(deploy); @@ -226,7 +226,7 @@ const deploy = makeCsprTransferDeploy({ transferAmount: '2500000000' // 2.5 CSPR }); -await deploy.sign(privateKey); +deploy.sign(privateKey); const rpcHandler = new HttpHandler('http://:7777/rpc'); const rpcClient = new RpcClient(rpcHandler); @@ -263,7 +263,7 @@ const deploy = makeAuctionManagerDeploy({ amount: '500000000000' // 500 CSPR }); -await deploy.sign(privateKey); +deploy.sign(privateKey); const rpcHandler = new HttpHandler('http://:7777/rpc'); const rpcClient = new RpcClient(rpcHandler); @@ -300,7 +300,7 @@ const deploy = await makeCep18TransferDeploy({ paymentAmount: '3000000000' // 3 CSPR }); -await deploy.sign(privateKey); +deploy.sign(privateKey); const rpcHandler = new HttpHandler('http://:7777/rpc'); const rpcClient = new RpcClient(rpcHandler); @@ -339,7 +339,7 @@ const deploy = await makeNftTransferDeploy({ tokenId: 234 }); -await deploy.sign(privateKey); +deploy.sign(privateKey); const rpcHandler = new HttpHandler('http://:7777/rpc'); const rpcClient = new RpcClient(rpcHandler); diff --git a/migration-guide-v2-v5.md b/migration-guide-v2-v5.md index 956ff2eda..22ce3f347 100644 --- a/migration-guide-v2-v5.md +++ b/migration-guide-v2-v5.md @@ -257,7 +257,7 @@ const transactionPayload = TransactionV1Payload.build({ const transaction = TransactionV1.makeTransactionV1( transactionPayload ); -await transaction.sign(privateKey); +transaction.sign(privateKey); ``` 6. **Submit Transaction**: @@ -301,7 +301,7 @@ const transaction = new NativeTransferBuilder() .payment(100_000_000) .build(); -await transaction.sign(privateKey); +transaction.sign(privateKey); try { const result = await rpcClient.putTransaction(transaction); diff --git a/src/types/Deploy.test.ts b/src/types/Deploy.test.ts index bf9502acd..2d4b94df8 100644 --- a/src/types/Deploy.test.ts +++ b/src/types/Deploy.test.ts @@ -59,8 +59,8 @@ describe('Deploy', () => { const payment = ExecutableDeployItem.standardPayment(paymentAmount); let deploy = Deploy.makeDeploy(deployHeader, payment, executableDeployItem); - await deploy.sign(senderKey); - await deploy.sign(recipientKey); + deploy.sign(senderKey); + deploy.sign(recipientKey); const json = Deploy.toJSON(deploy); diff --git a/src/types/Deploy.ts b/src/types/Deploy.ts index ddcfefc8f..599f44960 100644 --- a/src/types/Deploy.ts +++ b/src/types/Deploy.ts @@ -268,8 +268,8 @@ export class Deploy { * * @param keys The private key used to sign the deploy. */ - public async sign(keys: PrivateKey): Promise { - const signatureBytes = await keys.signAndAddAlgorithmBytes( + public sign(keys: PrivateKey): void { + const signatureBytes = keys.signAndAddAlgorithmBytes( this.hash.toBytes() ); const signature = new HexBytes(signatureBytes); diff --git a/src/types/Transaction.test.ts b/src/types/Transaction.test.ts index 222888715..00a8d93b7 100644 --- a/src/types/Transaction.test.ts +++ b/src/types/Transaction.test.ts @@ -76,7 +76,7 @@ describe('Test Transaction', () => { }); const transaction = TransactionV1.makeTransactionV1(transactionPayload); - await transaction.sign(keys); + transaction.sign(keys); const transactionPaymentAmount = transaction.payload.fields.args.args .get('amount')! @@ -108,7 +108,7 @@ describe('Test Transaction', () => { .payment(100_000_000) .build(); - await transaction.sign(sender); + transaction.sign(sender); const transactionV1 = transaction.getTransactionV1()!; const transactionPaymentAmount = transactionV1.payload.fields.args.args diff --git a/src/types/Transaction.ts b/src/types/Transaction.ts index ab3b6cbbd..af6bf227e 100644 --- a/src/types/Transaction.ts +++ b/src/types/Transaction.ts @@ -169,8 +169,8 @@ export class TransactionV1 { * Signs the transaction using the provided private key. * @param keys The private key to sign the transaction. */ - async sign(keys: PrivateKey): Promise { - const signatureBytes = await keys.signAndAddAlgorithmBytes( + sign(keys: PrivateKey): void { + const signatureBytes = keys.signAndAddAlgorithmBytes( this.hash.toBytes() ); const signature = new HexBytes(signatureBytes); @@ -493,8 +493,8 @@ export class Transaction { * Signs the transaction using the provided private key. * @param key The private key to sign the transaction. */ - async sign(key: PrivateKey): Promise { - const signatureBytes = await key.signAndAddAlgorithmBytes( + sign(key: PrivateKey): void { + const signatureBytes = key.signAndAddAlgorithmBytes( this.hash.toBytes() ); this.setSignature(signatureBytes, key.publicKey); @@ -566,9 +566,16 @@ export class Transaction { throw new Error("The JSON can't be parsed as a Transaction."); } - static toJSON(tx: Transaction) { - const serializer = new TypedJSON(Transaction); - return serializer.toPlainJson(tx); + toJSON() { + if (this.originTransactionV1) { + return TransactionV1.toJSON(this.originTransactionV1); + } + + if (this.originDeployV1) { + return Deploy.toJSON(this.originDeployV1) + } + + throw new Error('Incorrect Transaction instance. Missing origin value'); } } diff --git a/src/types/TransactionBuilder.md b/src/types/TransactionBuilder.md index d98e3de61..df699f81c 100644 --- a/src/types/TransactionBuilder.md +++ b/src/types/TransactionBuilder.md @@ -157,7 +157,7 @@ const transaction = new NativeTransferBuilder() .payment(100_000_000) .build(); -await transaction.sign(sender); +transaction.sign(sender); // Create a contract call const contractCallTransaction = new ContractCallBuilder() @@ -169,5 +169,5 @@ const contractCallTransaction = new ContractCallBuilder() .chainName('casper-net-1') .build(); -await contractCallTransaction.sign(sender); +contractCallTransaction.sign(sender); ``` diff --git a/src/types/clvalue/List.ts b/src/types/clvalue/List.ts index 90e2563dd..901cacdf4 100644 --- a/src/types/clvalue/List.ts +++ b/src/types/clvalue/List.ts @@ -163,6 +163,10 @@ export class CLValueList { bytes: innerBytes } = CLValueParser.fromBytesByType(remainder, clType.elementsType); + if (!inner) { + continue; + } + elements.push(inner); remainder = innerBytes; } diff --git a/src/types/clvalue/Map.ts b/src/types/clvalue/Map.ts index e0666cbb1..9484651b1 100644 --- a/src/types/clvalue/Map.ts +++ b/src/types/clvalue/Map.ts @@ -195,7 +195,7 @@ export class CLValueMap { const { result: u32, bytes: u32Bytes } = CLValueUInt32.fromBytes(bytes); const size = u32.toNumber(); - let remainder = u32Bytes; + const remainder = u32Bytes; if (size === 0) { return { result: mapResult, bytes: remainder }; @@ -205,11 +205,15 @@ export class CLValueMap { if (remainder.length) { const keyVal = CLValueParser.fromBytesByType(remainder, mapType.key); - remainder = keyVal?.bytes; + if (!keyVal.result) { + continue; + } const valVal = CLValueParser.fromBytesByType(remainder, mapType.val); - remainder = valVal.bytes; + if (!valVal.result) { + continue; + } mapResult.append(keyVal?.result, valVal?.result); } diff --git a/src/types/keypair/PrivateKey.ts b/src/types/keypair/PrivateKey.ts index d181108f9..55b81ef59 100644 --- a/src/types/keypair/PrivateKey.ts +++ b/src/types/keypair/PrivateKey.ts @@ -11,7 +11,7 @@ import { KeyAlgorithm } from './Algorithm'; */ export interface PrivateKeyInternal { /** Retrieves the public key bytes. */ - publicKeyBytes(): Promise; + publicKeyBytes(): Uint8Array; toBytes(): Uint8Array; /** @@ -19,7 +19,7 @@ export interface PrivateKeyInternal { * @param message - The message to sign. * @returns A promise resolving to the signature bytes. */ - sign(message: Uint8Array): Promise; + sign(message: Uint8Array): Uint8Array; /** Converts the private key to PEM format. */ toPem(): string; @@ -76,8 +76,8 @@ export class PrivateKey { * @param msg - The message to sign. * @returns A promise resolving to the signature bytes. */ - public async sign(msg: Uint8Array): Promise { - return await this.priv.sign(msg); + public sign(msg: Uint8Array): Uint8Array { + return this.priv.sign(msg); } /** @@ -85,8 +85,8 @@ export class PrivateKey { * @param msg - The message to sign. * @returns A promise resolving to the signature bytes with the algorithm byte. */ - public async signAndAddAlgorithmBytes(msg: Uint8Array): Promise { - const signature = await this.priv.sign(msg); + public signAndAddAlgorithmBytes(msg: Uint8Array): Uint8Array { + const signature = this.priv.sign(msg); const algBytes = Uint8Array.of(this.alg); return concat([algBytes, signature]); } @@ -105,9 +105,9 @@ export class PrivateKey { * @param algorithm - The cryptographic algorithm to use. * @returns A promise resolving to a new PrivateKey instance. */ - public static async generate(algorithm: KeyAlgorithm): Promise { - const priv = await PrivateKeyFactory.createPrivateKey(algorithm); - const pubBytes = await priv.publicKeyBytes(); + public static generate(algorithm: KeyAlgorithm): PrivateKey { + const priv = PrivateKeyFactory.createPrivateKey(algorithm); + const pubBytes = priv.publicKeyBytes(); const algBytes = Uint8Array.of(algorithm); const pub = PublicKey.fromBuffer(concat([algBytes, pubBytes])); return new PrivateKey(algorithm, pub, priv); @@ -139,15 +139,15 @@ export class PrivateKey { * @param algorithm - The cryptographic algorithm to use. * @returns A promise resolving to a PrivateKey instance. */ - public static async fromHex( + public static fromHex( key: string, algorithm: KeyAlgorithm - ): Promise { - const priv = await PrivateKeyFactory.createPrivateKeyFromHex( + ): PrivateKey { + const priv = PrivateKeyFactory.createPrivateKeyFromHex( key, algorithm ); - const pubBytes = await priv.publicKeyBytes(); + const pubBytes = priv.publicKeyBytes(); const algBytes = Uint8Array.of(algorithm); const pub = PublicKey.fromBuffer(concat([algBytes, pubBytes])); return new PrivateKey(algorithm, pub, priv); @@ -165,9 +165,9 @@ class PrivateKeyFactory { * @returns A promise resolving to a PrivateKeyInternal instance. * @throws Error if the algorithm is unsupported. */ - public static async createPrivateKey( + public static createPrivateKey( algorithm: KeyAlgorithm - ): Promise { + ): PrivateKeyInternal { switch (algorithm) { case KeyAlgorithm.ED25519: return Ed25519PrivateKey.generate(); @@ -206,10 +206,10 @@ class PrivateKeyFactory { * @returns A promise resolving to a PrivateKeyInternal instance. * @throws Error if the algorithm is unsupported. */ - public static async createPrivateKeyFromHex( + public static createPrivateKeyFromHex( key: string, algorithm: KeyAlgorithm - ): Promise { + ): PrivateKeyInternal { switch (algorithm) { case KeyAlgorithm.ED25519: return Ed25519PrivateKey.fromHex(key); diff --git a/src/types/keypair/PublicKey.ts b/src/types/keypair/PublicKey.ts index d385c0f21..208d9e806 100644 --- a/src/types/keypair/PublicKey.ts +++ b/src/types/keypair/PublicKey.ts @@ -50,7 +50,7 @@ interface PublicKeyInternal { * @param sig - The signature to verify. * @returns A promise that resolves to a boolean indicating the validity of the signature. */ - verifySignature(message: Uint8Array, sig: Uint8Array): Promise; + verifySignature(message: Uint8Array, sig: Uint8Array): boolean; } /** @@ -233,15 +233,15 @@ export class PublicKey { * @returns A promise that resolves to a boolean indicating the validity of the signature. * @throws Error if the signature or public key is empty, or if the signature is invalid. */ - async verifySignature( + verifySignature( message: Uint8Array, sig: Uint8Array - ): Promise { + ): boolean { if (sig.length <= 1) throw ErrEmptySignature; if (!this.key) throw ErrEmptyPublicKey; const sigWithoutAlgByte = sig.slice(1); - const signature = await this.key.verifySignature( + const signature = this.key.verifySignature( message, sigWithoutAlgByte ); diff --git a/src/types/keypair/ed25519/PrivateKey.ts b/src/types/keypair/ed25519/PrivateKey.ts index b3d74e489..81ef8fdd9 100644 --- a/src/types/keypair/ed25519/PrivateKey.ts +++ b/src/types/keypair/ed25519/PrivateKey.ts @@ -1,5 +1,8 @@ import * as ed25519 from '@noble/ed25519'; import { PrivateKeyInternal } from "../PrivateKey"; +import { sha512 } from '@noble/hashes/sha512'; + +ed25519.utils.sha512Sync = (...m) => sha512(ed25519.utils.concatBytes(...m)); /** * Represents an Ed25519 private key, supporting key generation, signing, and PEM encoding. @@ -24,7 +27,7 @@ export class PrivateKey implements PrivateKeyInternal { * Generates a new random Ed25519 private key. * @returns A promise that resolves to a new PrivateKey instance. */ - static async generate(): Promise { + static generate(): PrivateKey { const keyPair = ed25519.utils.randomPrivateKey(); return new PrivateKey(keyPair); } @@ -33,8 +36,8 @@ export class PrivateKey implements PrivateKeyInternal { * Retrieves the byte array of the associated public key. * @returns A promise that resolves to the public key bytes. */ - async publicKeyBytes(): Promise { - return ed25519.getPublicKey(this.key); + publicKeyBytes(): Uint8Array { + return ed25519.sync.getPublicKey(this.key); } toBytes(): Uint8Array { @@ -46,8 +49,8 @@ export class PrivateKey implements PrivateKeyInternal { * @param message - The message to sign. * @returns A promise that resolves to the signature bytes. */ - async sign(message: Uint8Array): Promise { - return ed25519.sign(message, this.key); + sign(message: Uint8Array): Uint8Array { + return ed25519.sync.sign(message, this.key); } /** diff --git a/src/types/keypair/ed25519/PublicKey.ts b/src/types/keypair/ed25519/PublicKey.ts index 217416ad3..823c44ef9 100644 --- a/src/types/keypair/ed25519/PublicKey.ts +++ b/src/types/keypair/ed25519/PublicKey.ts @@ -38,8 +38,8 @@ export class PublicKey { verifySignature( message: Uint8Array, signature: Uint8Array - ): Promise { - return ed25519.verify(signature, message, this.key); + ): boolean { + return ed25519.sync.verify(signature, message, this.key); } /** diff --git a/src/types/keypair/secp256k1/PrivateKey.ts b/src/types/keypair/secp256k1/PrivateKey.ts index 7c5a15aee..277e18dc1 100644 --- a/src/types/keypair/secp256k1/PrivateKey.ts +++ b/src/types/keypair/secp256k1/PrivateKey.ts @@ -1,7 +1,11 @@ import * as secp256k1 from '@noble/secp256k1'; import { sha256 } from '@noble/hashes/sha256'; +import { hmac } from '@noble/hashes/hmac'; import { PrivateKeyInternal } from "../PrivateKey"; +secp256k1.utils.hmacSha256Sync = (k, ...m) => + hmac(sha256, k, secp256k1.utils.concatBytes(...m)); + /** PEM prefix for a private key. */ const PemPrivateKeyPrefix = '-----BEGIN PRIVATE KEY-----'; @@ -28,7 +32,7 @@ export class PrivateKey implements PrivateKeyInternal { * Generates a new random secp256k1 private key. * @returns A promise that resolves to a new PrivateKey instance. */ - static async generate(): Promise { + static generate(): PrivateKey { const privateKey = secp256k1.utils.randomPrivateKey(); return new PrivateKey(privateKey); } @@ -37,7 +41,7 @@ export class PrivateKey implements PrivateKeyInternal { * Retrieves the byte array of the public key in compressed format. * @returns A promise that resolves to the compressed public key bytes. */ - async publicKeyBytes(): Promise { + publicKeyBytes(): Uint8Array { return secp256k1.getPublicKey(this.key, true); } @@ -59,9 +63,9 @@ export class PrivateKey implements PrivateKeyInternal { * @param message - The message to sign. * @returns A promise that resolves to the signature bytes in compact format. */ - async sign(message: Uint8Array): Promise { + sign(message: Uint8Array): Uint8Array { const hash = sha256(message); - return await secp256k1.sign(hash, this.key, { der: false }); + return secp256k1.signSync(hash, this.key, { der: false }); } /** diff --git a/src/types/keypair/secp256k1/PublicKey.ts b/src/types/keypair/secp256k1/PublicKey.ts index b0832da8a..9f402bec2 100644 --- a/src/types/keypair/secp256k1/PublicKey.ts +++ b/src/types/keypair/secp256k1/PublicKey.ts @@ -36,10 +36,10 @@ export class PublicKey { * @param signature - The signature to verify. Supports both raw (64-byte R || S) and DER formats. * @returns A promise that resolves to `true` if the signature is valid, or `false` otherwise. */ - async verifySignature( + verifySignature( message: Uint8Array, signature: Uint8Array - ): Promise { + ): boolean { let compactSignature: Uint8Array; if (signature.length === 64) { diff --git a/src/utils/deprecated-clPublicKey.ts b/src/utils/deprecated-clPublicKey.ts new file mode 100644 index 000000000..f9ece2f92 --- /dev/null +++ b/src/utils/deprecated-clPublicKey.ts @@ -0,0 +1,196 @@ +import { concat } from '@ethersproject/bytes'; +import { + CLType, + CLTypePublicKey, + CLValue, + Conversions, + PublicKey +} from '../types'; +import { SignatureAlgorithm } from './deprecated-keys'; +import { byteHash } from '../types/ByteConverters'; + +const ED25519_LENGTH = 32; +const SECP256K1_LENGTH = 33; + +/** @deprecated */ +export enum CLPublicKeyTag { + ED25519 = 1, + SECP256K1 = 2 +} + +/** @deprecated use {@link PublicKey} */ +export class CLPublicKey extends CLValue { + data: Uint8Array; + tag: CLPublicKeyTag; + private _publicKey: PublicKey; + + get pk(): PublicKey { + return this._publicKey; + } + + /** @deprecated */ + constructor( + rawPublicKey: Uint8Array, + tag: CLPublicKeyTag | SignatureAlgorithm + ) { + super(CLTypePublicKey); + + // NOTE Two ifs because of the legacy indentifiers in ./Keys + if (tag === CLPublicKeyTag.ED25519 || tag === SignatureAlgorithm.Ed25519) { + if (rawPublicKey.length !== ED25519_LENGTH) { + throw new Error( + `Wrong length of ED25519 key. Expected ${ED25519_LENGTH}, but got ${rawPublicKey.length}.` + ); + } + this.data = rawPublicKey; + this.tag = CLPublicKeyTag.ED25519; + + const algorithmIdentifier = CLPublicKeyTag[this.tag]; + const separator = Uint8Array.from([0]); + const prefix = Buffer.concat([ + Buffer.from(algorithmIdentifier.toLowerCase()), + separator + ]); + + this._publicKey = PublicKey.fromBytes( + // TODO check if this.data contains prefix. If yes -> remove concat + byteHash(concat([prefix, this.data])) + ).result; + return; + } + + if ( + tag === CLPublicKeyTag.SECP256K1 || + tag === SignatureAlgorithm.Secp256K1 + ) { + if (rawPublicKey.length !== SECP256K1_LENGTH) { + throw new Error( + `Wrong length of SECP256K1 key. Expected ${SECP256K1_LENGTH}, but got ${rawPublicKey.length}.` + ); + } + this.data = rawPublicKey; + this.tag = CLPublicKeyTag.SECP256K1; + + const algorithmIdentifier = CLPublicKeyTag[this.tag]; + const separator = Uint8Array.from([0]); + const prefix = Buffer.concat([ + Buffer.from(algorithmIdentifier.toLowerCase()), + separator + ]); + + this._publicKey = PublicKey.fromBytes( + byteHash(concat([prefix, this.data])) // TODO check if this.data contains prefix. If yes -> remove concat + ).result; + return; + } + + throw new Error('Unsupported type of public key'); + } + + /** @deprecated */ + clType(): CLType { + return CLTypePublicKey; + } + + /** @deprecated */ + isEd25519(): boolean { + return this.tag === CLPublicKeyTag.ED25519; + } + + /** @deprecated */ + isSecp256K1(): boolean { + return this.tag === CLPublicKeyTag.SECP256K1; + } + + /** @deprecated */ + toHex(checksummed = true): string { + return this._publicKey.toHex(checksummed); + } + + /** @deprecated */ + toAccountHash(): Uint8Array { + return this._publicKey.accountHash().toBytes(); + } + + /** @deprecated */ + toAccountHashStr(): string { + const bytes = this.toAccountHash(); + const hashHex = Buffer.from(bytes).toString('hex'); + return `account-hash-${hashHex}`; + } + + /** @deprecated */ + toAccountRawHashStr(): string { + const bytes = this.toAccountHash(); + const hashHex = Buffer.from(bytes).toString('hex'); + return hashHex; + } + + /** @deprecated */ + value(): Uint8Array { + return this.data; + } + + /** @deprecated */ + static fromEd25519(publicKey: Uint8Array): CLPublicKey { + return new CLPublicKey(publicKey, CLPublicKeyTag.ED25519); + } + + /** @deprecated */ + static fromSecp256K1(publicKey: Uint8Array): CLPublicKey { + return new CLPublicKey(publicKey, CLPublicKeyTag.SECP256K1); + } + + /** + * @deprecated + * Tries to decode PublicKey from its hex-representation. + * The hex format should be as produced by CLPublicKey.toHex + * @param publicKeyHex public key hex string contains key tag + * @param checksummed throws an Error if true and given string is not checksummed + */ + static fromHex(publicKeyHex: string, checksummed = false): CLPublicKey { + if (publicKeyHex.length < 2) { + throw new Error('Asymmetric key error: too short'); + } + + if (!/^0(1[0-9a-fA-F]{64}|2[0-9a-fA-F]{66})$/.test(publicKeyHex)) { + throw new Error('Invalid public key'); + } + + if (!PublicKey.isChecksummed(publicKeyHex)) { + console.warn( + 'Provided public key is not checksummed. Please check if you provide valid public key. You can generate checksummed public key from CLPublicKey.toHex(true).' + ); + if (checksummed) throw Error('Provided public key is not checksummed.'); + } + + const publicKeyHexBytes = Conversions.decodeBase16(publicKeyHex); + + return new CLPublicKey(publicKeyHexBytes.subarray(1), publicKeyHexBytes[0]); + } + + /** @deprecated */ + getTag(): CLPublicKeyTag { + return this.tag; + } + + /** @deprecated */ + getSignatureAlgorithm(): SignatureAlgorithm { + const mapTagToSignatureAlgorithm = ( + tag: CLPublicKeyTag + ): SignatureAlgorithm => { + const signatureAlgorithm = { + [CLPublicKeyTag.ED25519]: SignatureAlgorithm.Ed25519, + [CLPublicKeyTag.SECP256K1]: SignatureAlgorithm.Secp256K1 + }[tag]; + + if (signatureAlgorithm === undefined) { + throw Error('Unknown tag to signature algo mapping.'); + } + + return signatureAlgorithm; + }; + + return mapTagToSignatureAlgorithm(this.tag); + } +} diff --git a/src/utils/deprecated-deploy-utils.ts b/src/utils/deprecated-deploy-utils.ts new file mode 100644 index 000000000..c88d87592 --- /dev/null +++ b/src/utils/deprecated-deploy-utils.ts @@ -0,0 +1,442 @@ +import { Result, Ok, Err } from 'ts-results'; +import { concat } from '@ethersproject/bytes'; +import { BigNumber, BigNumberish } from '@ethersproject/bignumber'; +import fetch from 'node-fetch'; +import { + Approval, + CLValue, + Conversions, + DEFAULT_DEPLOY_TTL, + Deploy, + DeployHeader, + Duration, + ExecutableDeployItem, + Hash, + HexBytes, + Timestamp +} from '../types'; +import { CLPublicKey } from './deprecated-clPublicKey'; +import { byteHash, toBytesU32 } from '../types/ByteConverters'; +import { AsymmetricKey, validateSignature } from './deprecated-keys'; +import {humanizerTTL, dehumanizerTTL } from '../types/SerializationUtils' + +export {humanizerTTL, dehumanizerTTL}; + +/** + * @deprecated + * An object containing a unique address constructed from the `transferId` of a `Deploy` + */ +export class UniqAddress { + /** The `CLPublicKey` representation of the transacting account */ + publicKey: CLPublicKey; + + /** A transaction nonce */ + transferId: BigNumber; + + /** + * Constructs UniqAddress from the transacting account's `CLPublicKey` and unique transferId. + * @param publicKey CLPublicKey instance + * @param transferId BigNumberish value (can be also string representing number). Max U64. + */ + constructor(publicKey: CLPublicKey, transferId: BigNumberish) { + if (!(publicKey instanceof CLPublicKey)) { + throw new Error('publicKey is not an instance of CLPublicKey'); + } + const bigNum = BigNumber.from(transferId); + if (bigNum.gt('18446744073709551615')) { + throw new Error('transferId max value is U64'); + } + this.transferId = bigNum; + this.publicKey = publicKey; + } + + /** + * Stringifies the `UniqAddress` + * @returns string with the format "accountHex-transferIdHex" + */ + toString(): string { + return `${this.publicKey.toHex()}-${this.transferId.toHexString()}`; + } + + /** + * @deprecated + * Builds UniqAddress from string + * @param value `UniqAddress` string representation in the format "accountHex-transferIdHex" + * @returns A new `UniqAddress` + */ + static fromString(value: string): UniqAddress { + const [accountHex, transferHex] = value.split('-'); + const publicKey = CLPublicKey.fromHex(accountHex); + return new UniqAddress(publicKey, transferHex); + } +} + +/** + * @deprecated use {@link DeployHeader.toBytes} + * Serializes a `DeployHeader` into an array of bytes + * @param deployHeader + * @returns A serialized representation of the provided `DeployHeader` + */ +export const serializeHeader = (deployHeader: DeployHeader): Uint8Array => { + return deployHeader.toBytes(); +}; + +/** + * @deprecated + * Serializes the body of a deploy into an array of bytes + * @param payment Payment logic for use in a deployment + * @param session Session logic of a deploy + * @returns `Uint8Array` typed byte array, containing the payment and session logic of a deploy + */ +export const serializeBody = ( + payment: ExecutableDeployItem, + session: ExecutableDeployItem +): Uint8Array => { + return concat([payment.bytes(), session.bytes()]); +}; + +/** + * @deprecated + * Serializes an array of `Approval`s into a `Uint8Array` typed byte array + * @param approvals An array of `Approval`s to be serialized + * @returns `Uint8Array` typed byte array that can be deserialized to an array of `Approval`s + */ +export const serializeApprovals = (approvals: Approval[]): Uint8Array => { + const len = toBytesU32(approvals.length); + const bytes = concat( + approvals.map(approval => { + return concat([ + approval.signer.bytes(), + approval.signature.bytes, + ]); + }) + ); + return concat([len, bytes]); +}; + +/** + * @deprecated + * enum of supported contract types + * @enum + */ +export enum ContractType { + /** A pure WebAssembly representation of a smart contract */ + WASM = 'WASM', + /** A linked contract by hash */ + Hash = 'Hash', + /** A linked contract by name */ + Name = 'Name' +} + +/** + * The parameters of a `Deploy` object + * @deprecated use {@link Deploy.makeDeploy} + * */ +export class DeployParams { + /** + * Container for `Deploy` construction options. + * @param accountPublicKey The public key of the deploying account as a `CLPublicKey` + * @param chainName Name of the chain, to avoid the `Deploy` from being accidentally or maliciously included in a different chain. + * @param gasPrice Conversion rate between the cost of Wasm opcodes and the motes sent by the payment code, where 1 mote = 1 * 10^-9 CSPR + * @param ttl Time that the `Deploy` will remain valid for, in milliseconds. The default value is 1800000, which is 30 minutes + * @param dependencies Hex-encoded `Deploy` hashes of deploys which must be executed before this one. + * @param timestamp Note that timestamp is UTC, not local. + */ + constructor( + public accountPublicKey: CLPublicKey, + public chainName: string, + public gasPrice: number = 1, + public ttl: number = DEFAULT_DEPLOY_TTL, + public dependencies: Uint8Array[] = [], + public timestamp?: number + ) { + this.dependencies = dependencies.filter( + d => + dependencies.filter( + t => Conversions.encodeBase16(d) === Conversions.encodeBase16(t) + ).length < 2 + ); + } +} + +/** + * @deprecated use {@link Deploy.makeDeploy} + * Builds a `Deploy` object from `DeployParams`, session logic, and payment logic + * @param deployParam The parameters of the deploy, see [DeployParams](#L1323) + * @param session The session logic of the deploy + * @param payment The payment logic of the deploy + * @returns A new `Deploy` object + */ +export function makeDeploy( + deployParam: DeployParams, + session: ExecutableDeployItem, + payment: ExecutableDeployItem +): Deploy { + const serializedBody = serializeBody(payment, session); + const bodyHash = byteHash(serializedBody); + + if (!deployParam.timestamp) { + deployParam.timestamp = Date.now(); + } + + const header: DeployHeader = new DeployHeader( + deployParam.chainName, + deployParam.dependencies.map(d => new Hash(d)), + deployParam.gasPrice, + new Timestamp(new Date(deployParam.timestamp!)), + new Duration(deployParam.ttl), + deployParam.accountPublicKey.pk, + new Hash(bodyHash) + ); + + const serializedHeader = serializeHeader(header); + const deployHash = byteHash(serializedHeader); + + return new Deploy(new Hash(deployHash), header, payment, session, []); +} + +type TimeJSON = { + unixtime: number; +}; + + +class TimeService { + constructor(public url: string) {} + + async getTime(): Promise { + const result = await fetch(this.url); + const json = await result.json(); + + return json as TimeJSON; + } +} + +const TIME_API_URL = `worldtimeapi.org/api/timezone/UTC`; + +/** + * @deprecated + * Builds a `Deploy` object from `DeployParams`, session logic, and payment logic. + * If there is no timestamp in `DeployParams` it fetches it from the TimeService. + * Recommened to use in browser environment. + * @param deployParam The parameters of the deploy, see [DeployParams](#L1323) + * @param session The session logic of the deploy + * @param payment The payment logic of the deploy + * @returns A new `Deploy` object + */ +export async function makeDeployWithAutoTimestamp( + deployParam: DeployParams, + session: ExecutableDeployItem, + payment: ExecutableDeployItem +): Promise { + if (!deployParam.timestamp && typeof window !== 'undefined') { + const timeService = new TimeService( + `${location.protocol}//${TIME_API_URL}` + ); + const { unixtime } = await timeService.getTime(); + deployParam.timestamp = unixtime; + } + + return makeDeploy(deployParam, session, payment); +} + +/** + * @deprecated use {@link Deploy.sign} + * Uses the provided key pair to sign the Deploy message + * @param deploy Either an unsigned `Deploy` object or one with other signatures + * @param signingKey The keypair used to sign the `Deploy` + */ +export const signDeploy = ( + deploy: Deploy, + signingKey: AsymmetricKey +): Deploy => { + const approval = new Approval( + signingKey.publicKey.pk, + new HexBytes(signingKey.sign(deploy.hash.toBytes())) + ); + + deploy.approvals.push(approval); + + return deploy; +}; + +/** + * @deprecated use {@link Deploy.setSignature} + * Sets the algorithm of the already generated signature + * + * @param deploy A `Deploy` to be signed with `sig` + * @param sig the Ed25519 or Secp256K1 signature + * @param publicKey the public key used to generate the signature + */ +export const setSignature = ( + deploy: Deploy, + sig: Uint8Array, + publicKey: CLPublicKey +): Deploy => { + const approval = new Approval(publicKey.pk, new HexBytes(sig)); + + deploy.approvals.push(approval); + return deploy; +}; + +/** + * @deprecated use {@link ExecutableDeployItem.standardPayment} + * Creates an instance of standard payment logic + * + * @param paymentAmount The amount of motes to be used to pay for gas + * @returns A standard payment, as an `ExecutableDeployItem` to be attached to a `Deploy` + */ +export const standardPayment = ExecutableDeployItem.standardPayment; + +/** + * @deprecated use {@link Deploy.toJSON} + * Convert the deploy object to a JSON representation + * + * @param deploy The `Deploy` object to convert to JSON + * @returns A JSON version of the `Deploy`, which can be converted back later + */ +export const deployToJson = (deploy: Deploy) => { + return { + deploy: Deploy.toJSON(deploy) + }; +}; + +/** + * @deprecated use {@link Deploy.fromJson} + * Convert a JSON representation of a deploy to a `Deploy` object + * + * @param json A JSON representation of a `Deploy` + * @returns A `Result` that collapses to a `Deploy` or an error string + */ +export const deployFromJson = (json: any): Result => { + if (json.deploy === undefined) { + return new Err(new Error("The Deploy JSON doesn't have 'deploy' field.")); + } + let deploy = null; + try { + deploy = Deploy.fromJSON(json.deploy); + } catch (serializationError) { + return new Err(serializationError); + } + + if (deploy === undefined || deploy === null) { + return Err(new Error("The JSON can't be parsed as a Deploy.")); + } + + const valid = validateDeploy(deploy); + + if (valid.err) { + return new Err(new Error(valid.val)); + } + + return new Ok(deploy); +}; + +/** + * @deprecated use {@link Deploy.session}.setArg + * Adds a runtime argument to a `Deploy` object + * @param deploy The `Deploy` object for which to add the runtime argument + * @param name The name of the runtime argument + * @param value The value of the runtime argument + * @returns The original `Deploy` with the additional runtime argument + * @remarks Will fail if the `Deploy` has already been signed + */ +export const addArgToDeploy = ( + deploy: Deploy, + name: string, + value: CLValue +): Deploy => { + if (deploy.approvals.length !== 0) { + throw Error('Can not add argument to already signed deploy.'); + } + + deploy.session.setArg(name, value); + + return deploy; +}; + +/** + * @deprecated + * Gets the byte-size of a deploy + * @param deploy The `Deploy` for which to calculate the size + * @returns The size of the `Deploy` in its serialized representation + */ +export const deploySizeInBytes = (deploy: Deploy): number => { + const hashSize = deploy.hash.toBytes().length; + const bodySize = serializeBody(deploy.payment, deploy.session).length; + const headerSize = serializeHeader(deploy.header).length; + const approvalsSize = deploy.approvals + .map(approval => { + return ( + (approval.signature.bytes.length + approval.signer.bytes().length) / 2 + ); + }) + .reduce((a, b) => a + b, 0); + + return hashSize + headerSize + bodySize + approvalsSize; +}; + +/** + * @deprecated use {@link Deploy.validate} + * Validate a `Deploy` by calculating and comparing its stored blake2b hash + * @param deploy A `Deploy` to be validated + * @returns A `Result` that collapses to a `Deploy` or an error string + */ +export const validateDeploy = (deploy: Deploy): Result => { + if (!(deploy instanceof Deploy)) { + return new Err("'deploy' is not an instance of Deploy class."); + } + + const serializedBody = serializeBody(deploy.payment, deploy.session); + const bodyHash = byteHash(serializedBody); + + if (!arrayEquals(deploy.header.bodyHash!.toBytes(), bodyHash)) { + return Err(`Invalid deploy: bodyHash mismatch. Expected: ${bodyHash}, + got: ${deploy.header.bodyHash}.`); + } + + const serializedHeader = serializeHeader(deploy.header); + const deployHash = byteHash(serializedHeader); + + if (!arrayEquals(deploy.hash.toBytes(), deployHash)) { + return Err(`Invalid deploy: hash mismatch. Expected: ${deployHash}, + got: ${deploy.hash}.`); + } + + const isProperlySigned = deploy.approvals.every(({ signer, signature }) => { + const pk = CLPublicKey.fromHex(signer.toHex(), false); + const signatureRaw = Conversions.decodeBase16(signature.toHex().slice(2)); + return validateSignature(deploy.hash.toBytes(), signatureRaw, pk); + }); + + if (!isProperlySigned) { + return Err('Invalid signature.'); + } else { + return Ok(deploy); + } +}; + +/** + * @deprecated + * Compares two `Uint8Array`s + * @param a The first `Uint8Array` + * @param b The second `Uint8Array` + * @returns `true` if the two `Uint8Array`s match, and `false` otherwise + */ +export const arrayEquals = (a: Uint8Array, b: Uint8Array): boolean => { + return a.length === b.length && a.every((val, index) => val === b[index]); +}; + +/** + * @deprecated + * Serializes a `Deploy` to a `Uint8Array` + * @param deploy The `Deploy` to be serialized + * @returns A `Uint8Array` serialization of the provided `Deploy` + */ +export const deployToBytes = (deploy: Deploy): Uint8Array => { + return concat([ + serializeHeader(deploy.header), + deploy.hash.toBytes(), + serializeBody(deploy.payment, deploy.session), + serializeApprovals(deploy.approvals) + ]); +}; diff --git a/src/utils/deprecated-keys.ts b/src/utils/deprecated-keys.ts new file mode 100644 index 000000000..4ff1c8cc7 --- /dev/null +++ b/src/utils/deprecated-keys.ts @@ -0,0 +1,720 @@ +import { KeyAlgorithm, PrivateKey, PublicKey } from '../types/keypair'; +import { Conversions } from '../types'; +import KeyEncoder from 'key-encoder'; +import * as fs from 'fs'; +import { CLPublicKey } from "./deprecated-clPublicKey"; + +/** + * @deprecated use {@link KeyAlgorithm} instead + * @enum + */ +export enum SignatureAlgorithm { + Ed25519 = 'ed25519', + Secp256K1 = 'secp256k1' +} + +const keyEncoder = new KeyEncoder('secp256k1'); + +const mapSignatureAlgorithmToKeyAlgorithm: Record< + SignatureAlgorithm, + KeyAlgorithm +> = { + [SignatureAlgorithm.Ed25519]: KeyAlgorithm.ED25519, + [SignatureAlgorithm.Secp256K1]: KeyAlgorithm.SECP256K1 +}; + +const ED25519_PEM_SECRET_KEY_TAG = 'PRIVATE KEY'; +const ED25519_PEM_PUBLIC_KEY_TAG = 'PUBLIC KEY'; + +/** @deprecated */ +export interface SignKeyPair { + publicKey: Uint8Array; // Array with 32-byte public key + secretKey: Uint8Array; // Array with 32-byte secret key +} + +/** @deprecated */ +export const getKeysFromHexPrivKey = ( + key: string, + variant: SignatureAlgorithm +): AsymmetricKey => { + const privateKey = PrivateKey.fromHex( + key, + mapSignatureAlgorithmToKeyAlgorithm[variant] + ); + + let keyPair: AsymmetricKey; + + if (variant === SignatureAlgorithm.Secp256K1) { + keyPair = new Secp256K1(privateKey.toBytes(), privateKey.publicKey.bytes()); + return keyPair; + } + + if (variant === SignatureAlgorithm.Ed25519) { + keyPair = new Ed25519({ + publicKey: privateKey.publicKey.bytes(), + secretKey: privateKey.toBytes() + }); + return keyPair; + } + + throw Error('Unsupported key type'); +}; + +/** + * @deprecated + * Reads in a base64 private key, ignoring the header: `-----BEGIN PUBLIC KEY-----` + * and footer: `-----END PUBLIC KEY-----` + * @param {string} content A .pem private key string with a header and footer + * @returns A base64 private key as a `Uint8Array` + * @remarks + * If the provided base64 `content` string does not include a header/footer, + * it will pass through this function unaffected + * @example + * Example PEM: + * + * ``` + * -----BEGIN PUBLIC KEY-----\r\n + * MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEj1fgdbpNbt06EY/8C+wbBXq6VvG+vCVD\r\n + * Nl74LvVAmXfpdzCWFKbdrnIlX3EFDxkd9qpk35F/kLcqV3rDn/u3dg==\r\n + * -----END PUBLIC KEY-----\r\n + * ``` + */ +export function readBase64WithPEM(content: string): Uint8Array { + const base64 = content + // there are two kinks of line-endings, CRLF(\r\n) and LF(\n) + // we need handle both + .split(/\r?\n/) + .filter(x => !x.startsWith('---')) + .join('') + // remove the line-endings in the end of content + .trim(); + return Conversions.decodeBase64(base64); +} + +/** @deprecated use {@link PublicKey.verifySignature} instead */ +export const validateSignature = ( + msg: Uint8Array, + signature: Uint8Array, + pk: CLPublicKey +): boolean => { + return pk.pk.verifySignature(msg, signature); +}; + +/** @deprecated use {@link PrivateKey} and {@link PublicKey} instead */ +export abstract class AsymmetricKey { + public readonly publicKey: CLPublicKey; + public readonly privateKey: Uint8Array; + protected readonly _privateKey: PrivateKey; + public readonly signatureAlgorithm: SignatureAlgorithm; + + /** + * @deprecated use {@link PrivateKey} and {@link PublicKey} instead + * Constructs an `AsymmetricKey` inherited object + * @param {Uint8Array} publicKey An account's public key as a byte array + * @param {Uint8Array} privateKey An account's private key as a byte array + * @param {SignatureAlgorithm} signatureAlgorithm The signature algorithm of the key. Currently supported are Ed25519 and Secp256k1 + */ + constructor( + publicKey: Uint8Array, + privateKey: Uint8Array, + signatureAlgorithm: SignatureAlgorithm + ) { + this.publicKey = new CLPublicKey(publicKey, signatureAlgorithm); + this.privateKey = privateKey; + this._privateKey = PrivateKey.fromHex( + Conversions.encodeBase16(privateKey), + mapSignatureAlgorithmToKeyAlgorithm[signatureAlgorithm] + ); + this.signatureAlgorithm = signatureAlgorithm; + } + + /** + * @deprecated use {@link PublicKey}.accountHash().toBytes() instead + * Computes the blake2b account hash of the public key + * @returns The account hash as a byte array + */ + public accountHash(): Uint8Array { + return this.publicKey.pk.accountHash().toBytes(); + } + + /** + * @deprecated use {@link PublicKey}.toHex() instead + * Gets the hexadecimal public key of the account + * @param {boolean} checksummed Indicates whether the public key should be checksummed, default: `true` + * @returns The public key of the `AsymmetricKey` as a hexadecimal string + */ + public accountHex(checksummed = true): string { + return this.publicKey.pk.toHex(checksummed); + } + + /** + * @deprecated + * Inserts the provided `content` and `tag` into a .pem compliant string + * @param tag The tag inserted on the END line + * @param content The base-64 PEM compliant private key + */ + protected toPem(tag: string, content: string) { + // prettier-ignore + return `-----BEGIN ${tag}-----\n` + + `${content}\n` + + `-----END ${tag}-----\n`; + } + + /** + * @deprecated + * Export the public key encoded as a .pem + */ + public abstract exportPublicKeyInPem(): string; + + /** + * @deprecated use {@link PrivateKey.toPem} + * Export the private key encoded as a .pem + */ + public abstract exportPrivateKeyInPem(): string; + + /** + * @deprecated use {@link PrivateKey.sign} + * Sign a message using this `AsymmetricKey`'s private key + * @param {Uint8Array} msg The message to be signed, as a byte array + * @returns A byte array containing the signed message + */ + public abstract sign(msg: Uint8Array): Uint8Array; + + /** + * @deprecated use {@link PublicKey.verifySignature} + * Validate the signature by comparing it to the provided message + * @param {Uint8Array} signature The signature as a byte array + * @param {Uint8Array} msg The original message to be validated + * @returns `true` if the signature is valid, `false` otherwise + */ + public abstract verify(signature: Uint8Array, msg: Uint8Array): boolean; +} + +/** + * @deprecated + * Ed25519 variant of `AsymmetricKey` + * @remarks + * Based on SignatureAlgorithm.scala + * @see [Documentation](https://docs.casper.network/concepts/accounts-and-keys/#eddsa-keys) + */ +export class Ed25519 extends AsymmetricKey { + /** + * @deprecated + * Constructs a new Ed25519 object from a `SignKeyPair` + * @param {SignKeyPair} keyPair An object containing the keys "publicKey" and "secretKey" with corresponding `ByteArray` values + */ + constructor(keyPair: SignKeyPair) { + if (keyPair.secretKey.length != 32) { + console.warn( + `You're using private key from old version, please use newly formatted key with 32 bytes length.` + ); + } + + super( + keyPair.publicKey, + Ed25519.parsePrivateKey(keyPair.secretKey), + SignatureAlgorithm.Ed25519 + ); + } + + /** + * @deprecated use {@link PrivateKey}.generate(KeyAlgorithm.ED25519) + * Generates a new Ed25519 key pair + * @returns A new `Ed25519` object + */ + public static new() { + const privateKey = PrivateKey.generate(KeyAlgorithm.ED25519); + const publicKey = privateKey.publicKey; + return new Ed25519({ + secretKey: privateKey.toBytes(), + publicKey: publicKey.bytes() + }); + } + + /** + * @deprecated use {@link PublicKey}.accountHash().toHex() + * Generate the accountHex for the Ed25519 public key + * @param publicKey + */ + public static accountHex(publicKey: Uint8Array): string { + return PublicKey.fromBytes(publicKey) + .result.accountHash() + .toHex(); + } + + /** + * @deprecated + * Parse the key pair from a public key file and the corresponding private key file + * @param {string} publicKeyPath Path of public key file + * @param {string} privateKeyPath Path of private key file + * @returns A new `AsymmetricKey` + */ + public static parseKeyFiles( + publicKeyPath: string, + privateKeyPath: string + ): AsymmetricKey { + const publicKey = Ed25519.parsePublicKeyFile(publicKeyPath); + const privateKey = Ed25519.parsePrivateKeyFile(privateKeyPath); + return new Ed25519({ + publicKey, + secretKey: privateKey + }); + } + + /** + * @deprecated use {@link PublicKey}.accountHash().toBytes() instead + * Generates the account hash of a Ed25519 public key + * @param {Uint8Array} publicKey An Ed25519 public key + * @returns The blake2b account hash of the public key + */ + public static accountHash(publicKey: Uint8Array): Uint8Array { + return PublicKey.fromBytes(publicKey) + .result.accountHash() + .toBytes(); + } + + /** + * @deprecated + * Construct a keypair from a public key and corresponding private key + * @param {Uint8Array} publicKey The public key of an Ed25519 account + * @param {Uint8Array} privateKey The private key of the same Ed25519 account + * @returns A new `Ed25519` keypair + */ + public static parseKeyPair( + publicKey: Uint8Array, + privateKey: Uint8Array + ): Ed25519 { + const keyPair = new Ed25519({ + publicKey: PublicKey.fromBytes(publicKey).result.bytes(), + secretKey: PrivateKey.fromHex( + Conversions.encodeBase16(privateKey), + KeyAlgorithm.ED25519 + ).toBytes() + }); + + return keyPair; + } + + /** @deprecated */ + public static parsePrivateKeyFile(path: string): Uint8Array { + return Ed25519.parsePrivateKey(Ed25519.readBase64File(path)); + } + + /** + * @deprecated + * Parses a file containing an Ed25519 public key + * @param {string} path The path to the public key file + * @returns A `Uint8Array` typed representation of the public key + * @see {@link Ed25519.parsePublicKey} + */ + public static parsePublicKeyFile(path: string): Uint8Array { + return Ed25519.parsePublicKey(Ed25519.readBase64File(path)); + } + + /** + * @deprecated + * Parses a byte array containing an Ed25519 private key + * @param {Uint8Array} bytes A private key as a byte array + * @returns A validated byte array containing the provided Ed25519 private key + * @see {@link Ed25519.parseKey} + */ + public static parsePrivateKey(bytes: Uint8Array) { + return Ed25519.parseKey(bytes, 0, 32); + } + + /** + * @deprecated + * Parses a byte array containing an Ed25519 public key + * @param {Uint8Array} bytes A public key in bytes + * @returns A validated byte array containing the provided Ed25519 public key + * @see {@link Ed25519.parseKey} + */ + public static parsePublicKey(bytes: Uint8Array) { + return Ed25519.parseKey(bytes, 32, 64); + } + + /** + * @deprecated + * Calls global {@link readBase64WithPEM} and returns the result + * @param {string} content A .pem private key string with a header and footer + * @returns The result of global `readBase64WithPEM` + * @see {@link readBase64WithPEM} + */ + public static readBase64WithPEM(content: string) { + return readBase64WithPEM(content); + } + + /** + * @deprecated + * Read the Base64 content of a file, ignoring PEM frames + * @param {string} path The path to the PEM file + * @returns The result of {@link Ed25519.readBase64WithPEM} after reading in the content as a `string` with `fs` + */ + private static readBase64File(path: string): Uint8Array { + const content = fs.readFileSync(path).toString(); + return Ed25519.readBase64WithPEM(content); + } + + /** + * @deprecated + * Parses and validates a key in a certain range "from" to "to" + * @param {Uint8Array} bytes The key to be parsed and validated + * @param {number} from The starting index from which to parse the key + * @param {number} to The ending index from which to parse the key + * @returns The parsed key + * @throws `Error` if the key is of an unexpected length + */ + private static parseKey(bytes: Uint8Array, from: number, to: number) { + const len = bytes.length; + // prettier-ignore + const key = + (len === 32) ? bytes : + (len === 64) ? Buffer.from(bytes).slice(from, to) : + (len > 32 && len < 64) ? Buffer.from(bytes).slice(len % 32) : + null; + if (key == null || key.length !== 32) { + throw Error(`Unexpected key length: ${len}`); + } + return key; + } + + /** + * @deprecated + * Convert this instance's private key to PEM format + * @returns A PEM compliant string containing this instance's private key + * @see {@link AsymmetricKey.toPem} + */ + public exportPrivateKeyInPem() { + // prettier-ignore + const derPrefix = Buffer.from([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32]); + const encoded = Conversions.encodeBase64( + Buffer.concat([derPrefix, Buffer.from(this.privateKey)]) + ); + + return this.toPem(ED25519_PEM_SECRET_KEY_TAG, encoded); + } + + /** + * @deprecated + * Convert this instance's public key to PEM format + * @returns A PEM compliant string containing this instance's public key + * @see {@link AsymmetricKey.toPem} + */ + public exportPublicKeyInPem() { + // prettier-ignore + const derPrefix = Buffer.from([48, 42, 48, 5, 6, 3, 43, 101, 112, 3, 33, 0]); + const encoded = Conversions.encodeBase64( + Buffer.concat([derPrefix, Buffer.from(this.publicKey.bytes())]) + ); + return this.toPem(ED25519_PEM_PUBLIC_KEY_TAG, encoded); + } + + /** + * @deprecated + * Sign a message by using this instance's keypair + * @param {Uint8Array} msg The message to be signed, as a byte array + * @returns `Uint8Array` typed signature of the provided `msg` + */ + public sign(msg: Uint8Array): Uint8Array { + return this._privateKey.sign(msg); + } + + /** + * @deprecated + * Verifies a signature given the signature and the original message + * @param {Uint8Array} signature The signed message as a byte array + * @param {Uint8Array} msg The original message as a byte array + * @returns 'true' if the message if valid, `false` otherwise + */ + public verify(signature: Uint8Array, msg: Uint8Array) { + return this.publicKey.pk.verifySignature(msg, signature); + } + + /** + * @deprecated + * Derive a public key from private key or seed phrase + * @param {Uint8Array} privateKey The private key or seed phrase from which to derive the public key + * @returns A `Uint8Array` public key generated deterministically from the provided private key or seed phrase + * @remarks Both secret keys and seed phrases may be used to derive the public key + */ + public static privateToPublicKey(privateKey: Uint8Array): Uint8Array { + return PrivateKey.fromHex( + Conversions.encodeBase16(privateKey), + KeyAlgorithm.ED25519 + ).publicKey.bytes(); + } + + /** + * @deprecated + * Restore Ed25519 keyPair from private key file + * @param {string} privateKeyPath The path to the private key file + * @returns An Ed25519 `AsymmetricKey` + * @see {@link Ed25519.parsePrivateKeyFile} + * @see {@link Ed25519.privateToPublicKey} + * @see {@link Ed25519.parseKeyPair} + */ + public static loadKeyPairFromPrivateFile(privateKeyPath: string) { + const privateKey = Ed25519.parsePrivateKeyFile(privateKeyPath); + const publicKey = Ed25519.privateToPublicKey(privateKey); + return Ed25519.parseKeyPair(publicKey, privateKey); + } +} + +/** + * @deprecated + * Secp256k1 variant of `AsymmetricKey` + * @privateRemarks + * Orignated from [Secp256k1](https://en.bitcoin.it/wiki/Secp256k1) to support Ethereum keys on the Casper. + * @see [Documentation](https://docs.casper.network/concepts/accounts-and-keys/#ethereum-keys) + */ +export class Secp256K1 extends AsymmetricKey { + /** + * @deprecated + * Constructs a new Secp256K1 object from a public key and a private key + * @param {Uint8Array} publicKey A secp256k1 public key + * @param {Uint8Array} privateKey A secp256k1 private key + */ + constructor(publicKey: Uint8Array, privateKey: Uint8Array) { + super(publicKey, privateKey, SignatureAlgorithm.Secp256K1); + } + + /** + * @deprecated + * Generate a new pseudorandom Secp256k1 key pair + * @returns A new `Secp256K1` object + */ + public static new() { + const privateKey = PrivateKey.generate(KeyAlgorithm.SECP256K1); + const publicKey = privateKey.publicKey; + return new Secp256K1(publicKey.bytes(), privateKey.toBytes()); + } + + /** + * @deprecated + * Parse the key pair from a public key file and the corresponding private key file + * @param {string} publicKeyPath Path of public key file + * @param {string} privateKeyPath Path of private key file + * @returns A new `Secp256K1` object + */ + public static parseKeyFiles( + publicKeyPath: string, + privateKeyPath: string + ): AsymmetricKey { + const publicKey = Secp256K1.parsePublicKeyFile(publicKeyPath); + const privateKey = Secp256K1.parsePrivateKeyFile(privateKeyPath); + return new Secp256K1(publicKey, privateKey); + } + + /** + * @deprecated use {@link PublicKey}.accountHash().toBytes() instead + * Generates the account hash of a secp256k1 public key + * @param {Uint8Array} publicKey A secp256k1 public key + * @returns The blake2b account hash of the public key + */ + public static accountHash(publicKey: Uint8Array): Uint8Array { + return PublicKey.fromBytes(publicKey).result.bytes(); + } + + /** + * @deprecated + * Converts a `Uint8Array` public key to hexadecimal format + * @param publicKey + * @remarks + * The returned public key hex will be prefixed with a "02" to indicate that it is of the secp256k1 variety + */ + public static accountHex(publicKey: Uint8Array): string { + return PublicKey.fromBytes(publicKey).result.toHex(); + } + + /** + * @deprecated + * Construct a keypair from a public key and corresponding private key + * @param {Uint8Array} publicKey The public key of a secp256k1 account + * @param {Uint8Array} privateKey The private key of the same secp256k1 account + * @returns A new `AsymmetricKey` keypair + */ + public static parseKeyPair( + publicKey: Uint8Array, + privateKey: Uint8Array, + originalFormat: 'raw' | 'der' + ): AsymmetricKey { + const publ = Secp256K1.parsePublicKey(publicKey, originalFormat); + const priv = Secp256K1.parsePrivateKey(privateKey, originalFormat); + // nacl expects that the private key will contain both. + return new Secp256K1(publ, priv); + } + + /** + * @deprecated + * Parses a file containing a secp256k1 private key + * @param {string} path The path to the private key file + * @returns A `Uint8Array` typed representation of the private key + * @see {@link Secp256K1.parsePrivateKey} + */ + public static parsePrivateKeyFile(path: string): Uint8Array { + return Secp256K1.parsePrivateKey(Secp256K1.readBase64File(path)); + } + + /** + * @deprecated + * Parses a file containing a secp256k1 public key + * @param {string} path The path to the public key file + * @returns A `Uint8Array` typed representation of the private key + * @see {@link Secp256K1.parsePublicKey} + */ + public static parsePublicKeyFile(path: string): Uint8Array { + return Secp256K1.parsePublicKey(Secp256K1.readBase64File(path)); + } + + /** + * @deprecated + * Parses a byte array containing an Ed25519 public key + * @param {Uint8Array} bytes A public key in bytes + * @param {string} [originalFormat=der] The original format of the private key. + * Options are "der" or "raw", meaning "derived" or "raw", indicating a seed phrase and + * a raw private key respectively. + * @returns A validated byte array containing the provided Ed25519 public key + * @privateRemarks Validate that "der" means derived and "raw" means a raw public key + */ + public static parsePublicKey( + bytes: Uint8Array, + originalFormat: 'der' | 'raw' = 'der' + ) { + let rawKeyHex: string; + if (originalFormat === 'der') { + rawKeyHex = keyEncoder.encodePublic(Buffer.from(bytes), 'der', 'raw'); + } else { + rawKeyHex = Conversions.encodeBase16(bytes); + } + + const publicKey = Uint8Array.from(Buffer.from(rawKeyHex, 'hex')); + + return publicKey; + } + + /** + * @deprecated + * Parses a byte array containing a secp256k1 private key + * @param {Uint8Array} bytes A private key as a byte array + * @param {string} [originalFormat=der] The original format of the private key. + * Options are "der" or "raw", meaning "derived" or "raw", indicating a seed phrase and + * a raw private key respectively. + * @returns A validated byte array containing the provided secp256k1 private key + * @privateRemarks Validate that "der" means derived and "raw" means a raw private key + */ + public static parsePrivateKey( + bytes: Uint8Array, + originalFormat: 'der' | 'raw' = 'der' + ) { + let rawKeyHex: string; + if (originalFormat === 'der') { + rawKeyHex = keyEncoder.encodePrivate(Buffer.from(bytes), 'der', 'raw'); + } else { + rawKeyHex = Conversions.encodeBase16(bytes); + } + + const privateKey = Buffer.from(rawKeyHex, 'hex'); + return privateKey; + } + + /** + * @deprecated + * Calls global {@link readBase64WithPEM} and returns the result + * @param {string} content A .pem private key string with a header and footer + * @returns The result of global `readBase64WithPEM` + * @see {@link readBase64WithPEM} + */ + public static readBase64WithPEM(content: string) { + return readBase64WithPEM(content); + } + + /** + * @deprecated + * Read the Base64 content of a file, ignoring PEM frames + * @param {string} path The path to the PEM file + * @returns The result of {@link Secp256K1.readBase64WithPEM} after reading in the content as a `string` with `fs` + */ + private static readBase64File(path: string): Uint8Array { + const content = fs.readFileSync(path).toString(); + return Secp256K1.readBase64WithPEM(content); + } + + /** + * @deprecated + * Convert this instance's private key to PEM format + * @returns A PEM compliant string containing this instance's private key + */ + public exportPrivateKeyInPem(): string { + return keyEncoder.encodePrivate( + Conversions.encodeBase16(this.privateKey), + 'raw', + 'pem' + ); + } + + /** + * @deprecated + * Convert this instance's public key to PEM format + * @returns A PEM compliant string containing this instance's public key + */ + public exportPublicKeyInPem(): string { + return keyEncoder.encodePublic( + Conversions.encodeBase16(this.publicKey.bytes()), + 'raw', + 'pem' + ); + } + + /** + * @deprecated + * Sign a message by using this instance's keypair + * @param {Uint8Array} msg The message to be signed, as a byte array + * @returns `Uint8Array` typed signature of the provided `msg` + * @see [secp256k1.ecdsaSign](https://github.com/cryptocoinjs/secp256k1-node/blob/HEAD/API.md#ecdsasignmessage-uint8array-privatekey-uint8array--data-noncefn---data-uint8array-noncefn-message-uint8array-privatekey-uint8array-algo-null-data-uint8array-counter-number--uint8array----output-uint8array--len-number--uint8array--signature-uint8array-recid-number-) + */ + public sign(msg: Uint8Array): Uint8Array { + return this._privateKey.sign(msg); + } + + /** + * @deprecated + * Verifies a signature given the signature and the original message + * @param {Uint8Array} signature The signed message as a byte array + * @param {Uint8Array} msg The original message as a byte array + * @see [secp256k1.ecdsaVerify](https://github.com/cryptocoinjs/secp256k1-node/blob/HEAD/API.md#ecdsaverifysignature-uint8array-message-uint8array-publickey-uint8array-boolean) + * @returns 'true' if the message if valid, `false` otherwise + * @privateRemarks Need to document return and return type + */ + public verify(signature: Uint8Array, msg: Uint8Array) { + return this.publicKey.pk.verifySignature(msg, signature); + } + + /** + * @deprecated + * Derive a public key from private key + * @param {Uint8Array} privateKey The private key from which to derive the public key + * @returns A `Uint8Array` public key generated deterministically from the provided private key + * @see [secp256k1.publicKeyCreate](https://github.com/cryptocoinjs/secp256k1-node/blob/HEAD/API.md#publickeycreateprivatekey-uint8array-compressed-boolean--true-output-uint8array--len-number--uint8array--len--new-uint8arraylen-uint8array) + */ + public static privateToPublicKey(privateKey: Uint8Array): Uint8Array { + return PrivateKey.fromHex( + Conversions.encodeBase16(privateKey), + KeyAlgorithm.SECP256K1 + ).publicKey.bytes(); + } + + /** + * @deprecated + * Restore secp256k1 keyPair from private key file + * @param {string} privateKeyPath The path to the private key file + * @returns A secp256k1 `AsymmetricKey` + * @see {@link Secp256K1.parsePrivateKeyFile} + * @see {@link Secp256K1.privateToPublicKey} + * @see {@link Secp256K1.parseKeyPair} + */ + public static loadKeyPairFromPrivateFile(privateKeyPath: string) { + const privateKey = Secp256K1.parsePrivateKeyFile(privateKeyPath); + const publicKey = Secp256K1.privateToPublicKey(privateKey); + return Secp256K1.parseKeyPair(publicKey, privateKey, 'raw'); + } +} diff --git a/src/utils/index.ts b/src/utils/index.ts index eb8e7604d..d5d023fb1 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,5 +1,11 @@ +import * as Keys from './deprecated-keys'; +import * as DeployUtil from './deprecated-deploy-utils'; + export * from './cspr-transfer'; export * from './auction-manager'; export * from './constants'; export * from './cep-18-transfer'; export * from './cep-nft-transfer'; +export * from './deprecated-clPublicKey'; + +export { Keys, DeployUtil };