Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated makeCep18TransferDeploy to use contractPackageHash instead of contractHash, PEM files creation and parsing unification with old SDK #486

Merged
merged 5 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/types/keypair/PrivateKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,15 @@ export class PrivateKey {
* @param algorithm - The cryptographic algorithm to use.
* @returns A promise resolving to a PrivateKey instance.
*/
public static async fromPem(
public static fromPem(
content: string,
algorithm: KeyAlgorithm
): Promise<PrivateKey> {
const priv = await PrivateKeyFactory.createPrivateKeyFromPem(
): PrivateKey {
const priv = PrivateKeyFactory.createPrivateKeyFromPem(
content,
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);
Expand Down Expand Up @@ -185,10 +185,10 @@ class PrivateKeyFactory {
* @returns A promise resolving to a PrivateKeyInternal instance.
* @throws Error if the algorithm is unsupported.
*/
public static async createPrivateKeyFromPem(
public static createPrivateKeyFromPem(
content: string,
algorithm: KeyAlgorithm
): Promise<PrivateKeyInternal> {
): PrivateKeyInternal {
switch (algorithm) {
case KeyAlgorithm.ED25519:
return Ed25519PrivateKey.fromPem(content);
Expand Down
69 changes: 51 additions & 18 deletions src/types/keypair/ed25519/PrivateKey.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import * as ed25519 from '@noble/ed25519';
import { PrivateKeyInternal } from "../PrivateKey";
import { PrivateKeyInternal } from '../PrivateKey';
import { sha512 } from '@noble/hashes/sha512';
import { Conversions } from '../../Conversions';
import { readBase64WithPEM } from '../utils';

ed25519.utils.sha512Sync = (...m) => sha512(ed25519.utils.concatBytes(...m));

const ED25519_PEM_SECRET_KEY_TAG = 'PRIVATE KEY';

/**
* Represents an Ed25519 private key, supporting key generation, signing, and PEM encoding.
* Provides methods for creating instances from byte arrays, hexadecimal strings, and PEM format.
Expand Down Expand Up @@ -89,13 +93,33 @@ export class PrivateKey implements PrivateKeyInternal {
* @returns A PEM-encoded string of the private key.
*/
toPem(): string {
const seed = this.key.slice(0, 32);

const prefix = Buffer.alloc(PrivateKey.PemFramePrivateKeyPrefixSize);
const fullKey = Buffer.concat([prefix, Buffer.from(seed)]);

const pemString = fullKey.toString('base64');
return `-----BEGIN PRIVATE KEY-----\n${pemString}\n-----END PRIVATE KEY-----`;
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.key)])
);

return (
`-----BEGIN ${ED25519_PEM_SECRET_KEY_TAG}-----\n` +
`${encoded}\n` +
`-----END ${ED25519_PEM_SECRET_KEY_TAG}-----\n`
);
}

/**
Expand All @@ -106,18 +130,27 @@ export class PrivateKey implements PrivateKeyInternal {
* @throws Error if the content cannot be properly parsed.
*/
static fromPem(content: string): PrivateKey {
const base64Content = content
.replace('-----BEGIN PRIVATE KEY-----', '')
.replace('-----END PRIVATE KEY-----', '')
.replace(/\n/g, '');
const fullKey = Buffer.from(base64Content, 'base64');
const privateKeyBytes = readBase64WithPEM(content);

const data = fullKey.slice(PrivateKey.PemFramePrivateKeyPrefixSize);
return new PrivateKey(
new Uint8Array(Buffer.from(PrivateKey.parsePrivateKey(privateKeyBytes)))
);
}

const seed = data.slice(-32);
const privateEdDSA = ed25519.utils.randomPrivateKey();
privateEdDSA.set(seed);
private static parsePrivateKey(bytes: Uint8Array) {
const len = bytes.length;

// prettier-ignore
const key =
(len === 32) ? bytes :
(len === 64) ? Buffer.from(bytes).slice(0, 32) :
(len > 32 && len < 64) ? Buffer.from(bytes).slice(len % 32) :
null;

if (key == null || key.length !== 32) {
throw Error(`Unexpected key length: ${len}`);
}

return new PrivateKey(privateEdDSA);
return key;
}
}
33 changes: 19 additions & 14 deletions src/types/keypair/secp256k1/PrivateKey.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import * as secp256k1 from '@noble/secp256k1';
import { sha256 } from '@noble/hashes/sha256';
import { hmac } from '@noble/hashes/hmac';
import { PrivateKeyInternal } from "../PrivateKey";
import { PrivateKeyInternal } from '../PrivateKey';
import KeyEncoder from 'key-encoder';
import { Conversions } from '../../Conversions';
import { readBase64WithPEM } from '../utils';

secp256k1.utils.hmacSha256Sync = (k, ...m) =>
hmac(sha256, k, secp256k1.utils.concatBytes(...m));

/** PEM prefix for a private key. */
const PemPrivateKeyPrefix = '-----BEGIN PRIVATE KEY-----';

/** PEM suffix for a private key. */
const PemPrivateKeySuffix = '-----END PRIVATE KEY-----';
const keyEncoder = new KeyEncoder('secp256k1');

/**
* Represents a secp256k1 private key, supporting key generation, signing, and PEM encoding.
Expand Down Expand Up @@ -111,8 +110,11 @@ export class PrivateKey implements PrivateKeyInternal {
* @returns A PEM-encoded string of the private key.
*/
toPem(): string {
const keyBase64 = Buffer.from(this.key).toString('base64');
return `${PemPrivateKeyPrefix}\n${keyBase64}\n${PemPrivateKeySuffix}`;
return keyEncoder.encodePrivate(
Conversions.encodeBase16(this.key),
'raw',
'pem'
);
}

/**
Expand All @@ -122,11 +124,14 @@ export class PrivateKey implements PrivateKeyInternal {
* @throws Error if the content cannot be properly parsed.
*/
static fromPem(content: string): PrivateKey {
const base64Key = content
.replace(PemPrivateKeyPrefix, '')
.replace(PemPrivateKeySuffix, '')
.replace(/\s+/g, '');
const keyBuffer = Buffer.from(base64Key, 'base64');
return new PrivateKey(new Uint8Array(keyBuffer));
const privateKeyBytes = readBase64WithPEM(content);

const rawKeyHex = keyEncoder.encodePrivate(
Buffer.from(privateKeyBytes),
'der',
'raw'
);

return new PrivateKey(new Uint8Array(Buffer.from(rawKeyHex, 'hex')));
}
}
31 changes: 31 additions & 0 deletions src/types/keypair/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Conversions } from '../Conversions';

/**
* 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);
}
16 changes: 8 additions & 8 deletions src/utils/cep-18-transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import {
Key,
KeyTypeID,
PublicKey,
StoredContractByHash
StoredVersionedContractByHash
} from '../types';
import { CasperNetworkName } from '../@types';

export interface IMakeCep18TransferDeployParams {
contractHash: string;
contractPackageHash: string;
senderPublicKeyHex: string;
recipientPublicKeyHex: string;
transferAmount: string;
Expand All @@ -29,8 +29,8 @@ export interface IMakeCep18TransferDeployParams {
* This function generates a `Deploy` for transferring CEP-18 from one account to another.
*
* @param params - The parameters required to create the CEP-18 transfer deploy.
* @param params.contractHash - The hash of the contract to interact with.
* This is a 64-character hexadecimal string representing the contract.
* @param params.contractPackageHash - The hash of the contract package to interact with.
* This is a 64-character hexadecimal string representing the contract package.
* @param params.senderPublicKeyHex - The sender's public key in hexadecimal format.
* @param params.recipientPublicKeyHex - The recipient's public key in hexadecimal format.
* @param params.transferAmount - The amount of CSPR to transfer.
Expand Down Expand Up @@ -62,7 +62,7 @@ export interface IMakeCep18TransferDeployParams {
*/

export const makeCep18TransferDeploy = ({
contractHash,
contractPackageHash,
senderPublicKeyHex,
recipientPublicKeyHex,
transferAmount,
Expand All @@ -75,8 +75,8 @@ export const makeCep18TransferDeploy = ({

const session = new ExecutableDeployItem();

session.storedContractByHash = new StoredContractByHash(
ContractHash.newContract(contractHash),
session.storedVersionedContractByHash = new StoredVersionedContractByHash(
ContractHash.newContract(contractPackageHash),
'transfer',
Args.fromMap({
recipient: CLValue.newCLKey(
Expand All @@ -86,7 +86,7 @@ export const makeCep18TransferDeploy = ({
)
),
amount: CLValueUInt256.newCLUInt256(transferAmount)
})
}),
);

const payment = ExecutableDeployItem.standardPayment(paymentAmount);
Expand Down
Loading