Skip to content
This repository was archived by the owner on Apr 27, 2023. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all 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
5 changes: 2 additions & 3 deletions libs/deso-protocol/src/lib/transactions/social.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
TransactionMetadataUpdateProfile,
uvarint64ToBuf,
} from '@deso-core/identity';
import { utils as ecUtils } from '@noble/secp256k1';
import {
ConstructedTransactionResponse,
CreateFollowTxnStatelessRequest,
Expand All @@ -36,7 +35,7 @@ import {
ConstructedAndSubmittedTx,
TypeWithOptionalFeesAndExtraData,
} from '../types';
import { hexToBytes } from '@noble/hashes/utils';
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';

/**
* https://docs.deso.org/deso-backend/construct-transactions/social-transactions-api#update-profile
Expand Down Expand Up @@ -541,5 +540,5 @@ export const constructUpdateGroupChatMessageTransaction = (
function hexEncodePlainText(plainText: string) {
const textEncoder = new TextEncoder();
const bytes = textEncoder.encode(plainText);
return ecUtils.bytesToHex(new Uint8Array(bytes));
return bytesToHex(new Uint8Array(bytes));
}
4 changes: 2 additions & 2 deletions libs/identity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"wallet"
],
"dependencies": {
"@noble/secp256k1": "^1.7.1",
"@noble/hashes": "^1.2.0",
"@noble/curves": "^1.0.0",
"@noble/hashes": "^1.3.0",
"bs58": "^5.0.0",
"deso-protocol-types": "0.7.3",
"ethers": "^5.6.6",
Expand Down
91 changes: 46 additions & 45 deletions libs/identity/src/lib/crypto-utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { sha256 } from '@noble/hashes/sha256';
import { secp256k1 } from '@noble/curves/secp256k1';
import {
getPublicKey,
getSharedSecret as nobleGetSharedSecret,
Point,
sign as ecSign,
utils as ecUtils,
} from '@noble/secp256k1';
randomBytes,
bytesToHex,
hexToBytes,
concatBytes,
} from '@noble/hashes/utils';
import * as bs58 from 'bs58';
import { PUBLIC_KEY_PREFIXES } from './constants';
import { jwtAlgorithm, KeyPair, Network } from './types';
Expand Down Expand Up @@ -85,19 +85,19 @@ interface Base58CheckOptions {
// randomly generated 32 byte value (Uint8Array of length 32 or hex string of
// length 64)
export const keygen = (seed?: string | Uint8Array): KeyPair => {
const privateKey = seed ? normalizeSeed(seed) : ecUtils.randomBytes(32);
const seedHex = ecUtils.bytesToHex(privateKey);
const privateKey = seed ? normalizeSeed(seed) : randomBytes(32);
const seedHex = bytesToHex(privateKey);

return {
seedHex,
private: privateKey,
public: getPublicKey(privateKey, true /* isCompressed */),
public: secp256k1.getPublicKey(privateKey, true /* isCompressed */),
};
};

const normalizeSeed = (seed: string | Uint8Array): Uint8Array => {
if (typeof seed === 'string') {
return ecUtils.hexToBytes(seed);
return hexToBytes(seed);
} else {
return seed;
}
Expand All @@ -109,7 +109,7 @@ const normalizeSeed = (seed: string | Uint8Array): Uint8Array => {
* @returns
*/
export const sha256X2 = (data: Uint8Array | string): Uint8Array => {
const d = typeof data === 'string' ? ecUtils.hexToBytes(data) : data;
const d = typeof data === 'string' ? hexToBytes(data) : data;
return sha256(sha256(d));
};

Expand All @@ -131,12 +131,9 @@ export interface SignOptions {
}

const sign = (msgHashHex: string, privateKey: Uint8Array) => {
return ecSign(msgHashHex, privateKey, {
// For details about the signing options see: https://github.com/paulmillr/noble-secp256k1#signmsghash-privatekey
canonical: true,
der: true,
return secp256k1.sign(msgHashHex, privateKey, {
lowS: true,
extraEntropy: true,
recovered: true,
});
};

Expand All @@ -145,32 +142,32 @@ export const signTx = async (
seedHex: string,
options?: SignOptions
): Promise<string> => {
const transactionBytes = ecUtils.hexToBytes(txHex);
const transactionBytes = hexToBytes(txHex);
const [_, v1FieldsBuffer] = TransactionV0.fromBytes(transactionBytes);
const signatureIndex = transactionBytes.length - v1FieldsBuffer.length - 1;
const v0FieldsWithoutSignature = transactionBytes.slice(0, signatureIndex);
const hashedTxBytes = sha256X2(transactionBytes);
const transactionHashHex = ecUtils.bytesToHex(hashedTxBytes);
const privateKey = ecUtils.hexToBytes(seedHex);
const [signatureBytes, recoveryParam] = await sign(
transactionHashHex,
privateKey
);
const transactionHashHex = bytesToHex(hashedTxBytes);
const privateKey = hexToBytes(seedHex);
const sig = await sign(transactionHashHex, privateKey);

const signatureBytes = sig.toDERRawBytes();
const recoveryParam = sig.recovery as number;

const signatureLength = uvarint64ToBuf(signatureBytes.length);

if (options?.isDerivedKey) {
signatureBytes[0] += 1 + recoveryParam;
}

const signedTransactionBytes = ecUtils.concatBytes(
const signedTransactionBytes = concatBytes(
v0FieldsWithoutSignature,
signatureLength,
signatureBytes,
v1FieldsBuffer
);

return ecUtils.bytesToHex(signedTransactionBytes);
return bytesToHex(signedTransactionBytes);
};

export const getSignedJWT = async (
Expand All @@ -195,11 +192,11 @@ export const getSignedJWT = async (
});

const jwt = `${urlSafeBase64(header)}.${urlSafeBase64(payload)}`;
const [signature] = await sign(
ecUtils.bytesToHex(sha256(new Uint8Array(new TextEncoder().encode(jwt)))),
ecUtils.hexToBytes(seedHex)
const sig = await sign(
bytesToHex(sha256(new Uint8Array(new TextEncoder().encode(jwt)))),
hexToBytes(seedHex)
);
const encodedSignature = derToJoseEncoding(signature);
const encodedSignature = derToJoseEncoding(sig.toDERRawBytes());

return `${jwt}.${encodedSignature}`;
};
Expand All @@ -217,15 +214,15 @@ export const encryptChatMessage = async (
recipientPublicKeyBase58Check: string,
message: string
) => {
const privateKey = ecUtils.hexToBytes(senderSeedHex);
const privateKey = hexToBytes(senderSeedHex);
const recipientPublicKey = bs58PublicKeyToBytes(
recipientPublicKeyBase58Check
);
const sharedPrivateKey = await getSharedPrivateKey(
privateKey,
recipientPublicKey
);
const sharedPublicKey = getPublicKey(sharedPrivateKey);
const sharedPublicKey = secp256k1.getPublicKey(sharedPrivateKey);

return encrypt(sharedPublicKey, message);
};
Expand All @@ -239,15 +236,15 @@ export const encrypt = async (
publicKey: Uint8Array | string,
plaintext: string
): Promise<string> => {
const ephemPrivateKey = ecUtils.randomBytes(32);
const ephemPublicKey = getPublicKey(ephemPrivateKey);
const ephemPrivateKey = randomBytes(32);
const ephemPublicKey = secp256k1.getPublicKey(ephemPrivateKey);
const publicKeyBytes =
typeof publicKey === 'string'
? await bs58PublicKeyToBytes(publicKey)
: publicKey;
const privKey = await getSharedPrivateKey(ephemPrivateKey, publicKeyBytes);
const encryptionKey = privKey.slice(0, 16);
const iv = ecUtils.randomBytes(16);
const iv = randomBytes(16);
const macKey = sha256(privKey.slice(16));
const bytes = new TextEncoder().encode(plaintext);
const cryptoKey = await globalThis.crypto.subtle.importKey(
Expand All @@ -266,12 +263,12 @@ export const encrypt = async (
cryptoKey,
bytes
);
const hmac = await ecUtils.hmacSha256(
const hmac = secp256k1.CURVE.hmac(
macKey,
new Uint8Array([...iv, ...new Uint8Array(cipherBytes)])
);

return ecUtils.bytesToHex(
return bytesToHex(
new Uint8Array([
...ephemPublicKey,
...iv,
Expand All @@ -287,7 +284,9 @@ export const bs58PublicKeyToCompressedBytes = (str: string) => {
}
const pubKeyUncompressed = bs58PublicKeyToBytes(str);
return Uint8Array.from(
Point.fromHex(ecUtils.bytesToHex(pubKeyUncompressed)).toRawBytes(true)
secp256k1.ProjectivePoint.fromHex(
bytesToHex(pubKeyUncompressed)
).toRawBytes(true)
);
};

Expand All @@ -306,7 +305,9 @@ export const bs58PublicKeyToBytes = (str: string) => {
throw new Error('Invalid checksum');
}

return Point.fromHex(ecUtils.bytesToHex(payload.slice(3))).toRawBytes(false);
return secp256k1.ProjectivePoint.fromHex(
bytesToHex(payload.slice(3))
).toRawBytes(false);
};

const isValidHmac = (candidate: Uint8Array, knownGood: Uint8Array) => {
Expand All @@ -328,7 +329,7 @@ export const decryptChatMessage = async (
publicDecryptionKey: string,
cipherTextHex: string
) => {
const privateKey = ecUtils.hexToBytes(recipientSeedHex);
const privateKey = hexToBytes(recipientSeedHex);
const publicKey = await bs58PublicKeyToBytes(publicDecryptionKey);
const sharedPrivateKey = await getSharedPrivateKey(privateKey, publicKey);
return decrypt(sharedPrivateKey, cipherTextHex);
Expand All @@ -338,7 +339,7 @@ export const decrypt = async (
privateDecryptionKey: Uint8Array | string,
cipherTextHex: string
) => {
const cipherBytes = ecUtils.hexToBytes(cipherTextHex);
const cipherBytes = hexToBytes(cipherTextHex);
const metaLength = 113;

if (cipherBytes.length < metaLength) {
Expand All @@ -359,7 +360,7 @@ export const decrypt = async (
const sharedSecretKey = await getSharedPrivateKey(privateKey, ephemPublicKey);
const encryptionKey = sharedSecretKey.slice(0, 16);
const macKey = sha256(sharedSecretKey.slice(16));
const hmacKnownGood = await ecUtils.hmacSha256(macKey, cipherAndIv);
const hmacKnownGood = await secp256k1.CURVE.hmac(macKey, cipherAndIv);

if (!isValidHmac(msgMac, hmacKnownGood)) {
throw new Error('incorrect MAC');
Expand All @@ -386,17 +387,17 @@ export const getSharedPrivateKey = async (
privKey: Uint8Array,
pubKey: Uint8Array
) => {
const sharedSecret = await getSharedSecret(privKey, pubKey);
const sharedSecret = await secp256k1.getSharedSecret(privKey, pubKey);

return kdf(sharedSecret, 32);
};

export const decodePublicKey = async (publicKeyBase58Check: string) => {
const decoded = await bs58PublicKeyToBytes(publicKeyBase58Check);
const withPrefixRemoved = decoded.slice(3);
const senderPubKeyHex = ecUtils.bytesToHex(withPrefixRemoved);
const senderPubKeyHex = bytesToHex(withPrefixRemoved);

return Point.fromHex(senderPubKeyHex).toRawBytes(false);
return secp256k1.ProjectivePoint.fromHex(senderPubKeyHex).toRawBytes(false);
};

export const getSharedSecret = async (
Expand All @@ -406,7 +407,7 @@ export const getSharedSecret = async (
// passing true to compress the public key, and then slicing off the first byte
// matches the implementation of derive in the elliptic package.
// https://github.com/paulmillr/noble-secp256k1/issues/28#issuecomment-946538037
return nobleGetSharedSecret(privKey, pubKey, true).slice(1);
return secp256k1.getSharedSecret(privKey, pubKey, true).slice(1);
};

// taken from reference implementation in the deso chat app:
Expand Down
15 changes: 8 additions & 7 deletions libs/identity/src/lib/identity.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getPublicKey, utils as ecUtils } from '@noble/secp256k1';
import { secp256k1 } from '@noble/curves/secp256k1';
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
import { ChatType, NewMessageEntryResponse } from 'deso-protocol-types';
import { verify } from 'jsonwebtoken';
import KeyEncoder from 'key-encoder';
Expand All @@ -21,9 +22,9 @@ import {
} from './transaction-transcoders';

function getPemEncodePublicKey(privateKey: Uint8Array): string {
const publicKey = getPublicKey(privateKey, true);
const publicKey = secp256k1.getPublicKey(privateKey, true);
return new KeyEncoder('secp256k1').encodePublic(
ecUtils.bytesToHex(publicKey),
bytesToHex(publicKey),
'raw',
'pem'
);
Expand Down Expand Up @@ -95,7 +96,7 @@ describe('identity', () => {
});
const txBytes = exampleTransaction.toBytes();
return Promise.resolve({
TransactionHex: ecUtils.bytesToHex(txBytes),
TransactionHex: bytesToHex(txBytes),
});
}
return Promise.resolve(null);
Expand Down Expand Up @@ -362,7 +363,7 @@ describe('identity', () => {
const jwt = await identity.jwt();
const parsedAndVerifiedJwt = verify(
jwt,
getPemEncodePublicKey(ecUtils.hexToBytes(testDerivedSeedHex)),
getPemEncodePublicKey(hexToBytes(testDerivedSeedHex)),
{
// See: https://github.com/auth0/node-jsonwebtoken/issues/862
// tl;dr: the jsonwebtoken library doesn't support the ES256K algorithm,
Expand Down Expand Up @@ -393,7 +394,7 @@ describe('identity', () => {
const jwt = await identity.jwt();
let errorMessage = '';
try {
verify(jwt, getPemEncodePublicKey(ecUtils.hexToBytes(badSeedHex)), {
verify(jwt, getPemEncodePublicKey(hexToBytes(badSeedHex)), {
// See: https://github.com/auth0/node-jsonwebtoken/issues/862
// tl;dr: the jsonwebtoken library doesn't support the ES256K algorithm,
// even though this is the correct algorithm for JWTs signed
Expand Down Expand Up @@ -515,7 +516,7 @@ describe('identity', () => {
'lorem ipsum dolor sit amet, consectetur adipiscing elit';
const textEncoder = new TextEncoder();
const bytes = textEncoder.encode(plaintextMsg);
const hexEncodedMsg = ecUtils.bytesToHex(new Uint8Array(bytes));
const hexEncodedMsg = bytesToHex(new Uint8Array(bytes));

// we only need to set the active user to the recipient, since we're not
// decrypting anything.
Expand Down
12 changes: 7 additions & 5 deletions libs/identity/src/lib/identity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { keccak_256 } from '@noble/hashes/sha3';
import { Point, utils as ecUtils } from '@noble/secp256k1';
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
import { secp256k1 } from '@noble/curves/secp256k1';
import {
AccessGroupEntryResponse,
AuthorizeDerivedKeyRequest,
Expand Down Expand Up @@ -984,9 +985,9 @@ export class Identity {

desoAddressToEthereumAddress(address: string) {
const desoPKBytes = bs58PublicKeyToBytes(address).slice(1);
const ethPKHex = ecUtils.bytesToHex(keccak_256(desoPKBytes)).slice(24);
const ethPKHex = bytesToHex(keccak_256(desoPKBytes)).slice(24);
// EIP-55 requires a checksum. Reference implementation: https://eips.ethereum.org/EIPS/eip-55
const checksum = ecUtils.bytesToHex(keccak_256(ethPKHex));
const checksum = bytesToHex(keccak_256(ethPKHex));

return Array.from(ethPKHex).reduce(
(ethAddress, char, index) =>
Expand Down Expand Up @@ -1093,7 +1094,8 @@ export class Identity {
);
}

const compressedEthKey = Point.fromHex(ethereumPublicKey).toRawBytes(true);
const compressedEthKey =
secp256k1.ProjectivePoint.fromHex(ethereumPublicKey).toRawBytes(true);
return publicKeyToBase58Check(compressedEthKey, { network: this.#network });
}

Expand Down Expand Up @@ -1677,7 +1679,7 @@ class DeSoCoreError extends Error {
}

const unencryptedHexToPlainText = (hex: string) => {
const bytes = ecUtils.hexToBytes(hex);
const bytes = hexToBytes(hex);
const textDecoder = new TextDecoder();
return textDecoder.decode(bytes);
};
Expand Down
4 changes: 2 additions & 2 deletions libs/identity/src/lib/noble-elliptic-compat-test.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as noble from '@noble/secp256k1';
import { secp256k1 } from '@noble/curves/secp256k1';
import * as bs58check from 'bs58check';
import { ec as EC } from 'elliptic';

Expand All @@ -19,7 +19,7 @@ describe('@noble/sepc256k1 and elliptic.ec compatibility', () => {
const ecBs58CheckPubKey = bs58check.encode(ecDesoPubKey);

// noble key pair
const noblePubKeyCompressed = noble.getPublicKey(seedHex, true);
const noblePubKeyCompressed = secp256k1.getPublicKey(seedHex, true);
const nobleDesoPubKey = new Uint8Array([
...desoMainNetPrefix,
...noblePubKeyCompressed,
Expand Down
Loading