From 85f07ce1d9dbd0885c86e6953a70ff36e94d9ef0 Mon Sep 17 00:00:00 2001 From: janniks Date: Mon, 27 Nov 2023 15:22:43 +0100 Subject: [PATCH] feat!: remove wrapped private key type BREAKING CHANGE: Remove wrapped `StacksPrivateKey` type in favor of simple type alias for string/Uint8Array. --- packages/common/src/constants.ts | 2 + packages/encryption/src/keys.ts | 11 ++- packages/transactions/src/authorization.ts | 44 ++++----- packages/transactions/src/builders.ts | 76 +++++++-------- packages/transactions/src/keys.ts | 96 +++++++++---------- packages/transactions/src/signature.ts | 2 +- packages/transactions/src/signer.ts | 10 +- .../src/structuredDataSignature.ts | 5 +- packages/transactions/src/transaction.ts | 13 ++- packages/transactions/src/types.ts | 68 ++++++------- .../transactions/tests/authorization.test.ts | 7 +- packages/transactions/tests/builder.test.ts | 80 +++++++--------- packages/transactions/tests/clarity.test.ts | 9 +- packages/transactions/tests/keys.test.ts | 79 ++++++--------- packages/transactions/tests/macros.ts | 7 +- .../tests/structuredDataSignature.test.ts | 9 +- .../transactions/tests/transaction.test.ts | 65 ++++++------- 17 files changed, 272 insertions(+), 311 deletions(-) diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts index d9c13c111..c4d33ecde 100644 --- a/packages/common/src/constants.ts +++ b/packages/common/src/constants.ts @@ -4,6 +4,8 @@ export const DEVNET_URL = 'http://localhost:3999'; export const GAIA_URL = 'https://hub.blockstack.org'; +// todo: deduplicate magic variables + /** @ignore internal */ export const PRIVATE_KEY_COMPRESSED_LENGTH = 33; diff --git a/packages/encryption/src/keys.ts b/packages/encryption/src/keys.ts index 56d88eb9b..8be7686e6 100644 --- a/packages/encryption/src/keys.ts +++ b/packages/encryption/src/keys.ts @@ -12,6 +12,7 @@ import { import base58 from 'bs58'; import { hashRipemd160 } from './hashRipemd160'; import { hashSha256Sync } from './sha2Hash'; +import { PrivateKey } from '../../transactions/src'; const BITCOIN_PUBKEYHASH = 0x00; @@ -96,7 +97,7 @@ export function publicKeyToBtcAddress( * @ignore * @returns a compressed public key */ -export function getPublicKeyFromPrivate(privateKey: string | Uint8Array): string { +export function getPublicKeyFromPrivate(privateKey: PrivateKey): string { const privateKeyBytes = privateKeyToBytes(privateKey); // for backwards compatibility we always return a compressed public key, regardless of private key mode return bytesToHex(nobleGetPublicKey(privateKeyBytes.slice(0, 32), true)); @@ -105,8 +106,8 @@ export function getPublicKeyFromPrivate(privateKey: string | Uint8Array): string /** * @ignore */ -export function ecSign(messageHash: Uint8Array, hexPrivateKey: string | Uint8Array) { - return signSync(messageHash, privateKeyToBytes(hexPrivateKey).slice(0, 32), { +export function ecSign(messageHash: Uint8Array, privateKey: PrivateKey) { + return signSync(messageHash, privateKeyToBytes(privateKey).slice(0, 32), { der: false, }); } @@ -114,14 +115,14 @@ export function ecSign(messageHash: Uint8Array, hexPrivateKey: string | Uint8Arr /** * @ignore */ -export function isValidPrivateKey(privateKey: string | Uint8Array): boolean { +export function isValidPrivateKey(privateKey: PrivateKey): boolean { return utils.isValidPrivateKey(privateKeyToBytes(privateKey)); } /** * @ignore */ -export function compressPrivateKey(privateKey: string | Uint8Array): Uint8Array { +export function compressPrivateKey(privateKey: PrivateKey): Uint8Array { const privateKeyBytes = privateKeyToBytes(privateKey); return privateKeyBytes.length == PRIVATE_KEY_COMPRESSED_LENGTH diff --git a/packages/transactions/src/authorization.ts b/packages/transactions/src/authorization.ts index 3fb85db82..b5d2e30f1 100644 --- a/packages/transactions/src/authorization.ts +++ b/packages/transactions/src/authorization.ts @@ -17,11 +17,22 @@ import { StacksMessageType, } from './constants'; -import { cloneDeep, leftPadHex, txidFromData } from './utils'; +import { BytesReader } from './bytesReader'; +import { MessageSignature } from './common'; +import { DeserializationError, SigningError, VerificationError } from './errors'; +import { + createStacksPublicKey, + PrivateKey, + privateKeyToPublic, + publicKeyFromSignatureVrs, + publicKeyIsCompressed, + signWithKey, + StacksPublicKey, +} from './keys'; import { - TransactionAuthField, - serializeMessageSignature, deserializeMessageSignature, + serializeMessageSignature, + TransactionAuthField, } from './signature'; import { addressFromPublicKeys, @@ -30,20 +41,7 @@ import { deserializeLPList, serializeLPList, } from './types'; - -import { - createStacksPublicKey, - getPublicKey, - isCompressed, - publicKeyFromSignatureVrs, - signWithKey, - StacksPrivateKey, - StacksPublicKey, -} from './keys'; - -import { MessageSignature } from './common'; -import { DeserializationError, SigningError, VerificationError } from './errors'; -import { BytesReader } from './bytesReader'; +import { cloneDeep, leftPadHex, txidFromData } from './utils'; export function emptyMessageSignature(): MessageSignature { return { @@ -133,7 +131,7 @@ export function createSingleSigSpendingCondition( 1, [createStacksPublicKey(pubKey)] ).hash160; - const keyEncoding = isCompressed(createStacksPublicKey(pubKey)) + const keyEncoding = publicKeyIsCompressed(pubKey) ? PubKeyEncoding.Compressed : PubKeyEncoding.Uncompressed; @@ -276,7 +274,7 @@ export function deserializeMultiSigSpendingCondition( for (const field of fields) { switch (field.contents.type) { case StacksMessageType.PublicKey: - if (!isCompressed(field.contents)) haveUncompressed = true; + if (!publicKeyIsCompressed(field.contents.data)) haveUncompressed = true; break; case StacksMessageType.MessageSignature: if (field.pubKeyEncoding === PubKeyEncoding.Uncompressed) haveUncompressed = true; @@ -363,7 +361,7 @@ function makeSigHashPostSign( // * the signature const hashLength = 32 + 1 + RECOVERABLE_ECDSA_SIG_LENGTH_BYTES; - const pubKeyEncoding = isCompressed(pubKey) + const pubKeyEncoding = publicKeyIsCompressed(pubKey.data) ? PubKeyEncoding.Compressed : PubKeyEncoding.Uncompressed; @@ -382,7 +380,7 @@ export function nextSignature( authType: AuthType, fee: IntegerType, nonce: IntegerType, - privateKey: StacksPrivateKey + privateKey: PrivateKey ): { nextSig: MessageSignature; nextSigHash: string; @@ -390,7 +388,7 @@ export function nextSignature( const sigHashPreSign = makeSigHashPreSign(curSigHash, authType, fee, nonce); const signature = signWithKey(privateKey, sigHashPreSign); - const publicKey = getPublicKey(privateKey); + const publicKey = createStacksPublicKey(privateKeyToPublic(privateKey)); const nextSigHash = makeSigHashPostSign(sigHashPreSign, publicKey, signature); return { @@ -491,7 +489,7 @@ function verifyMultiSig( switch (field.contents.type) { case StacksMessageType.PublicKey: - if (!isCompressed(field.contents)) haveUncompressed = true; + if (!publicKeyIsCompressed(field.contents.data)) haveUncompressed = true; foundPubKey = field.contents; break; case StacksMessageType.MessageSignature: diff --git a/packages/transactions/src/builders.ts b/packages/transactions/src/builders.ts index 2f523033a..3af24b958 100644 --- a/packages/transactions/src/builders.ts +++ b/packages/transactions/src/builders.ts @@ -1,4 +1,13 @@ -import { ApiOpts, ApiParam, bytesToHex, hexToBytes, IntegerType } from '@stacks/common'; +import { ApiOpts, ApiParam, IntegerType } from '@stacks/common'; +import { + STACKS_MAINNET, + STACKS_TESTNET, + StacksNetwork, + StacksNetworkName, + TransactionVersion, + networkFrom, + whenTransactionVersion, +} from '@stacks/network'; import { c32address } from 'c32check'; import { createSingleSigSpendingCondition, @@ -21,14 +30,7 @@ import { } from './constants'; import { ClarityAbi, validateContractCall } from './contract-abi'; import { estimateFee, getAbi, getNonce } from './fetch'; -import { - createStacksPrivateKey, - getPublicKey, - pubKeyfromPrivKey, - publicKeyFromBytes, - publicKeyToAddress, - publicKeyToString, -} from './keys'; +import { createStacksPublicKey, privateKeyToPublic, publicKeyToAddress } from './keys'; import { createContractCallPayload, createSmartContractPayload, @@ -41,26 +43,17 @@ import { } from './postcondition'; import { AssetInfo, - createContractPrincipal, - createStandardPrincipal, FungiblePostCondition, NonFungiblePostCondition, PostCondition, STXPostCondition, + createContractPrincipal, + createStandardPrincipal, } from './postcondition-types'; import { TransactionSigner } from './signer'; import { StacksTransaction } from './transaction'; import { createLPList } from './types'; import { defaultApiFromNetwork, omit } from './utils'; -import { - networkFrom, - STACKS_MAINNET, - STACKS_TESTNET, - StacksNetworkName, - TransactionVersion, - whenTransactionVersion, -} from '@stacks/network'; -import { StacksNetwork } from '@stacks/network'; export interface MultiSigOptions { numSignatures: number; @@ -185,11 +178,11 @@ export async function makeSTXTokenTransfer( ): Promise { if ('senderKey' in txOptions) { // txOptions is SignedTokenTransferOptions - const publicKey = publicKeyToString(getPublicKey(createStacksPrivateKey(txOptions.senderKey))); + const publicKey = privateKeyToPublic(txOptions.senderKey); const options = omit(txOptions, 'senderKey'); const transaction = await makeUnsignedSTXTokenTransfer({ publicKey, ...options }); - const privKey = createStacksPrivateKey(txOptions.senderKey); + const privKey = txOptions.senderKey; const signer = new TransactionSigner(transaction); signer.signOrigin(privKey); @@ -202,13 +195,13 @@ export async function makeSTXTokenTransfer( const signer = new TransactionSigner(transaction); let pubKeys = txOptions.publicKeys; for (const key of txOptions.signerKeys) { - const pubKey = pubKeyfromPrivKey(key); - pubKeys = pubKeys.filter(pk => pk !== bytesToHex(pubKey.data)); - signer.signOrigin(createStacksPrivateKey(key)); + const pubKey = privateKeyToPublic(key); + pubKeys = pubKeys.filter(pk => pk !== pubKey); + signer.signOrigin(key); } for (const key of pubKeys) { - signer.appendOrigin(publicKeyFromBytes(hexToBytes(key))); + signer.appendOrigin(createStacksPublicKey(key)); } return transaction; @@ -280,11 +273,12 @@ export async function makeContractDeploy( ): Promise { if ('senderKey' in txOptions) { // txOptions is SignedContractDeployOptions - const publicKey = publicKeyToString(getPublicKey(createStacksPrivateKey(txOptions.senderKey))); + + const publicKey = privateKeyToPublic(txOptions.senderKey); const options = omit(txOptions, 'senderKey'); const transaction = await makeUnsignedContractDeploy({ publicKey, ...options }); - const privKey = createStacksPrivateKey(txOptions.senderKey); + const privKey = txOptions.senderKey; const signer = new TransactionSigner(transaction); signer.signOrigin(privKey); @@ -297,13 +291,13 @@ export async function makeContractDeploy( const signer = new TransactionSigner(transaction); let pubKeys = txOptions.publicKeys; for (const key of txOptions.signerKeys) { - const pubKey = pubKeyfromPrivKey(key); - pubKeys = pubKeys.filter(pk => pk !== bytesToHex(pubKey.data)); - signer.signOrigin(createStacksPrivateKey(key)); + const pubKey = privateKeyToPublic(key); + pubKeys = pubKeys.filter(pk => pk !== pubKey); + signer.signOrigin(key); } for (const key of pubKeys) { - signer.appendOrigin(publicKeyFromBytes(hexToBytes(key))); + signer.appendOrigin(createStacksPublicKey(key)); } return transaction; @@ -522,11 +516,11 @@ export async function makeContractCall( txOptions: SignedContractCallOptions | SignedMultiSigContractCallOptions ): Promise { if ('senderKey' in txOptions) { - const publicKey = publicKeyToString(getPublicKey(createStacksPrivateKey(txOptions.senderKey))); + const publicKey = privateKeyToPublic(txOptions.senderKey); const options = omit(txOptions, 'senderKey'); const transaction = await makeUnsignedContractCall({ publicKey, ...options }); - const privKey = createStacksPrivateKey(txOptions.senderKey); + const privKey = txOptions.senderKey; const signer = new TransactionSigner(transaction); signer.signOrigin(privKey); @@ -538,13 +532,13 @@ export async function makeContractCall( const signer = new TransactionSigner(transaction); let pubKeys = txOptions.publicKeys; for (const key of txOptions.signerKeys) { - const pubKey = pubKeyfromPrivKey(key); - pubKeys = pubKeys.filter(pk => pk !== bytesToHex(pubKey.data)); - signer.signOrigin(createStacksPrivateKey(key)); + const pubKey = privateKeyToPublic(key); + pubKeys = pubKeys.filter(pk => pk !== pubKey); + signer.signOrigin(key); } for (const key of pubKeys) { - signer.appendOrigin(publicKeyFromBytes(hexToBytes(key))); + signer.appendOrigin(createStacksPublicKey(key)); } return transaction; @@ -744,7 +738,7 @@ export async function sponsorTransaction( const options = Object.assign(defaultOptions, sponsorOptions); options.api = defaultApiFromNetwork(options.network, sponsorOptions.api); - const sponsorPubKey = pubKeyfromPrivKey(options.sponsorPrivateKey); + const sponsorPubKey = privateKeyToPublic(options.sponsorPrivateKey); if (sponsorOptions.fee == null) { let txFee: bigint | number = 0; @@ -778,14 +772,14 @@ export async function sponsorTransaction( const sponsorSpendingCondition = createSingleSigSpendingCondition( options.sponsorAddressHashmode, - publicKeyToString(sponsorPubKey), + sponsorPubKey, options.sponsorNonce, options.fee ); options.transaction.setSponsor(sponsorSpendingCondition); - const privKey = createStacksPrivateKey(options.sponsorPrivateKey); + const privKey = options.sponsorPrivateKey; const signer = TransactionSigner.createSponsorSigner( options.transaction, sponsorSpendingCondition diff --git a/packages/transactions/src/keys.ts b/packages/transactions/src/keys.ts index b4aaed645..22544da2c 100644 --- a/packages/transactions/src/keys.ts +++ b/packages/transactions/src/keys.ts @@ -14,8 +14,8 @@ import { hexToBytes, intToHex, parseRecoverableSignatureVrs, - privateKeyToBytes, PRIVATE_KEY_COMPRESSED_LENGTH, + privateKeyToBytes, signatureRsvToVrs, signatureVrsToRsv, } from '@stacks/common'; @@ -61,30 +61,32 @@ export interface StacksPublicKey { /** Creates a P2PKH address string from the given private key and tx version. */ export function getAddressFromPrivateKey( /** Private key bytes or hex string */ - privateKey: string | Uint8Array, + privateKey: PrivateKey, transactionVersion = TransactionVersion.Mainnet ): string { - const pubKey = pubKeyfromPrivKey(privateKey); - return getAddressFromPublicKey(pubKey.data, transactionVersion); + const publicKey = privateKeyToPublic(privateKey); + return getAddressFromPublicKey(publicKey, transactionVersion); } +// todo: use network as last parameter instead of txversion param. next refactor /** Creates a P2PKH address string from the given public key and tx version. */ export function getAddressFromPublicKey( /** Public key bytes or hex string */ - publicKey: string | Uint8Array, + publicKey: PublicKey, transactionVersion = TransactionVersion.Mainnet ): string { - publicKey = typeof publicKey === 'string' ? publicKey : bytesToHex(publicKey); + publicKey = typeof publicKey === 'string' ? hexToBytes(publicKey) : publicKey; const addrVer = addressHashModeToVersion(AddressHashMode.SerializeP2PKH, transactionVersion); - const addr = addressFromVersionHash(addrVer, hashP2PKH(hexToBytes(publicKey))); + const addr = addressFromVersionHash(addrVer, hashP2PKH(publicKey)); const addrString = addressToString(addr); return addrString; } -export function createStacksPublicKey(key: string): StacksPublicKey { +export function createStacksPublicKey(publicKey: PublicKey): StacksPublicKey { + publicKey = typeof publicKey === 'string' ? hexToBytes(publicKey) : publicKey; return { type: StacksMessageType.PublicKey, - data: hexToBytes(key), + data: publicKey, }; } @@ -112,64 +114,62 @@ export function publicKeyFromSignatureRsv( ); } -export function publicKeyFromBytes(data: Uint8Array): StacksPublicKey { - return { type: StacksMessageType.PublicKey, data }; +export function privateKeyToHex(publicKey: PublicKey): string { + return typeof publicKey === 'string' ? publicKey : bytesToHex(publicKey); } +export const publicKeyToHex = privateKeyToHex; -export function isCompressed(key: StacksPublicKey): boolean { - return !bytesToHex(key.data).startsWith('04'); +export function privateKeyIsCompressed(privateKey: PrivateKey): boolean { + const length = typeof privateKey === 'string' ? privateKey.length / 2 : privateKey.byteLength; + return length === PRIVATE_KEY_COMPRESSED_LENGTH; } -export function publicKeyToString(key: StacksPublicKey): string { - return bytesToHex(key.data); +export function publicKeyIsCompressed(publicKey: PublicKey): boolean { + return !publicKeyToHex(publicKey).startsWith('04'); } export function serializePublicKey(key: StacksPublicKey): Uint8Array { return key.data.slice(); } -export function pubKeyfromPrivKey(privateKey: string | Uint8Array): StacksPublicKey { - const privKey = createStacksPrivateKey(privateKey); - const publicKey = nobleGetPublicKey(privKey.data.slice(0, 32), privKey.compressed); - return createStacksPublicKey(bytesToHex(publicKey)); +/** + * Get the public key from a private key. + * Allows for "compressed" and "uncompressed" private keys. + * > Matches legacy `pubKeyfromPrivKey`, `getPublic` function behavior + */ +export function privateKeyToPublic(privateKey: PrivateKey): string { + privateKey = privateKeyToBytes(privateKey); + const isCompressed = privateKeyIsCompressed(privateKey); + return bytesToHex(nobleGetPublicKey(privateKey.slice(0, 32), isCompressed)); } -export function compressPublicKey(publicKey: string | Uint8Array): StacksPublicKey { - const hex = typeof publicKey === 'string' ? publicKey : bytesToHex(publicKey); - const compressed = Point.fromHex(hex).toHex(true); - return createStacksPublicKey(compressed); +export function compressPublicKey(publicKey: PublicKey): string { + return Point.fromHex(publicKeyToHex(publicKey)).toHex(true); } export function deserializePublicKey(bytesReader: BytesReader): StacksPublicKey { const fieldId = bytesReader.readUInt8(); const keyLength = fieldId === 4 ? UNCOMPRESSED_PUBKEY_LENGTH_BYTES : COMPRESSED_PUBKEY_LENGTH_BYTES; - return publicKeyFromBytes(concatArray([fieldId, bytesReader.readBytes(keyLength)])); + return createStacksPublicKey(concatArray([fieldId, bytesReader.readBytes(keyLength)])); } -export interface StacksPrivateKey { - // "compressed" private key is a misnomer: https://web.archive.org/web/20220131144208/https://www.oreilly.com/library/view/mastering-bitcoin/9781491902639/ch04.html#comp_priv - // it actually means: should public keys be generated as "compressed" or "uncompressed" from this private key - compressed: boolean; - data: Uint8Array; +// todo: double-check for deduplication, rename! +export function makeRandomPrivKey(): string { + return bytesToHex(utils.randomPrivateKey()); } -export function createStacksPrivateKey(key: string | Uint8Array): StacksPrivateKey { - const data = privateKeyToBytes(key); - const compressed = data.length == PRIVATE_KEY_COMPRESSED_LENGTH; - return { data, compressed }; -} - -export function makeRandomPrivKey(): StacksPrivateKey { - return createStacksPrivateKey(utils.randomPrivateKey()); -} +// todo: complete refactor +export type PrivateKey = string | Uint8Array; +export type PublicKey = string | Uint8Array; /** * @deprecated The Clarity compatible {@link signMessageHashRsv} is preferred, but differs in signature format * @returns A recoverable signature (in VRS order) */ -export function signWithKey(privateKey: StacksPrivateKey, messageHash: string): MessageSignature { - const [rawSignature, recoveryId] = signSync(messageHash, privateKey.data.slice(0, 32), { +export function signWithKey(privateKey: PrivateKey, messageHash: string): MessageSignature { + privateKey = privateKeyToBytes(privateKey); + const [rawSignature, recoveryId] = signSync(messageHash, privateKey.slice(0, 32), { canonical: true, recovered: true, }); @@ -191,20 +191,14 @@ export function signMessageHashRsv({ privateKey, }: { messageHash: string; - privateKey: StacksPrivateKey; + privateKey: PrivateKey; }): MessageSignature { const messageSignature = signWithKey(privateKey, messageHash); return { ...messageSignature, data: signatureVrsToRsv(messageSignature.data) }; } -export function getPublicKey(privateKey: StacksPrivateKey): StacksPublicKey { - return pubKeyfromPrivKey(privateKey.data); -} - -export function privateKeyToString(privateKey: StacksPrivateKey): string { - return bytesToHex(privateKey.data); -} - -export function publicKeyToAddress(version: AddressVersion, publicKey: StacksPublicKey): string { - return c32address(version, bytesToHex(hash160(publicKey.data))); +// todo: use network as last parameter instead of addressversion param. next refactor +export function publicKeyToAddress(version: AddressVersion, publicKey: PublicKey): string { + publicKey = typeof publicKey === 'string' ? hexToBytes(publicKey) : publicKey; + return c32address(version, bytesToHex(hash160(publicKey))); } diff --git a/packages/transactions/src/signature.ts b/packages/transactions/src/signature.ts index e9c68816d..4b62465b4 100644 --- a/packages/transactions/src/signature.ts +++ b/packages/transactions/src/signature.ts @@ -96,7 +96,7 @@ export function serializeTransactionAuthField(field: TransactionAuthField): Uint bytesArray.push(serializePublicKey(field.contents)); } else { bytesArray.push(AuthFieldType.PublicKeyUncompressed); - bytesArray.push(serializePublicKey(compressPublicKey(field.contents.data))); + bytesArray.push(hexToBytes(compressPublicKey(field.contents.data))); } break; case StacksMessageType.MessageSignature: diff --git a/packages/transactions/src/signer.ts b/packages/transactions/src/signer.ts index 80d5eb366..ea5f960a0 100644 --- a/packages/transactions/src/signer.ts +++ b/packages/transactions/src/signer.ts @@ -1,10 +1,10 @@ import { StacksTransaction } from './transaction'; -import { StacksPrivateKey, StacksPublicKey } from './keys'; -import { isSingleSig, nextVerification, SpendingConditionOpts } from './authorization'; -import { cloneDeep } from './utils'; +import { SpendingConditionOpts, isSingleSig, nextVerification } from './authorization'; import { AuthType, PubKeyEncoding, StacksMessageType } from './constants'; import { SigningError } from './errors'; +import { PrivateKey, StacksPublicKey } from './keys'; +import { cloneDeep } from './utils'; export class TransactionSigner { transaction: StacksTransaction; @@ -68,7 +68,7 @@ export class TransactionSigner { return signer; } - signOrigin(privateKey: StacksPrivateKey) { + signOrigin(privateKey: PrivateKey) { if (this.checkOverlap && this.originDone) { throw new SigningError('Cannot sign origin after sponsor key'); } @@ -111,7 +111,7 @@ export class TransactionSigner { this.transaction.appendPubkey(publicKey); } - signSponsor(privateKey: StacksPrivateKey) { + signSponsor(privateKey: PrivateKey) { if (this.transaction.auth === undefined) { throw new SigningError('"transaction.auth" is undefined'); } diff --git a/packages/transactions/src/structuredDataSignature.ts b/packages/transactions/src/structuredDataSignature.ts index 3870a919d..819cc9f53 100644 --- a/packages/transactions/src/structuredDataSignature.ts +++ b/packages/transactions/src/structuredDataSignature.ts @@ -1,9 +1,8 @@ import { sha256 } from '@noble/hashes/sha256'; import { bytesToHex, concatBytes, utf8ToBytes } from '@stacks/common'; - import { ClarityType, ClarityValue, serializeCV } from './clarity'; import { StacksMessageType } from './constants'; -import { signMessageHashRsv, StacksPrivateKey } from './keys'; +import { PrivateKey, signMessageHashRsv } from './keys'; // Refer to SIP018 https://github.com/stacksgov/sips/ // > asciiToBytes('SIP018') @@ -83,7 +82,7 @@ export function signStructuredData({ }: { message: ClarityValue; domain: ClarityValue; - privateKey: StacksPrivateKey; + privateKey: PrivateKey; }): StructuredDataSignature { const structuredDataHash: string = bytesToHex(sha256(encodeStructuredData({ message, domain }))); diff --git a/packages/transactions/src/transaction.ts b/packages/transactions/src/transaction.ts index 02967e501..10b82b9c2 100644 --- a/packages/transactions/src/transaction.ts +++ b/packages/transactions/src/transaction.ts @@ -1,5 +1,4 @@ import { - bytesToHex, concatArray, hexToBytes, IntegerType, @@ -42,7 +41,7 @@ import { deserializePayload, Payload, PayloadInput, serializePayload } from './p import { createLPList, deserializeLPList, LengthPrefixedList, serializeLPList } from './types'; -import { isCompressed, StacksPrivateKey, StacksPublicKey } from './keys'; +import { PrivateKey, privateKeyIsCompressed, publicKeyIsCompressed, StacksPublicKey } from './keys'; import { BytesReader } from './bytesReader'; @@ -125,7 +124,7 @@ export class StacksTransaction { return verifyOrigin(this.auth, this.verifyBegin()); } - signNextOrigin(sigHash: string, privateKey: StacksPrivateKey): string { + signNextOrigin(sigHash: string, privateKey: PrivateKey): string { if (this.auth.spendingCondition === undefined) { throw new Error('"auth.spendingCondition" is undefined'); } @@ -135,7 +134,7 @@ export class StacksTransaction { return this.signAndAppend(this.auth.spendingCondition, sigHash, AuthType.Standard, privateKey); } - signNextSponsor(sigHash: string, privateKey: StacksPrivateKey): string { + signNextSponsor(sigHash: string, privateKey: PrivateKey): string { if (this.auth.authType === AuthType.Sponsored) { return this.signAndAppend( this.auth.sponsorSpendingCondition, @@ -151,7 +150,7 @@ export class StacksTransaction { appendPubkey(publicKey: StacksPublicKey) { const cond = this.auth.spendingCondition; if (cond && !isSingleSig(cond)) { - const compressed = isCompressed(publicKey); + const compressed = publicKeyIsCompressed(publicKey.data); cond.fields.push( createTransactionAuthField( compressed ? PubKeyEncoding.Compressed : PubKeyEncoding.Uncompressed, @@ -167,7 +166,7 @@ export class StacksTransaction { condition: SpendingConditionOpts, curSigHash: string, authType: AuthType, - privateKey: StacksPrivateKey + privateKey: PrivateKey ): string { const { nextSig, nextSigHash } = nextSignature( curSigHash, @@ -179,7 +178,7 @@ export class StacksTransaction { if (isSingleSig(condition)) { condition.signature = nextSig; } else { - const compressed = bytesToHex(privateKey.data).endsWith('01'); + const compressed = privateKeyIsCompressed(privateKey); condition.fields.push( createTransactionAuthField( compressed ? PubKeyEncoding.Compressed : PubKeyEncoding.Uncompressed, diff --git a/packages/transactions/src/types.ts b/packages/transactions/src/types.ts index b107f62d1..48d7f2c5d 100644 --- a/packages/transactions/src/types.ts +++ b/packages/transactions/src/types.ts @@ -8,55 +8,57 @@ import { intToHex, utf8ToBytes, } from '@stacks/common'; +import { StacksNetwork, StacksNetworkName, TransactionVersion } from '@stacks/network'; +import { BytesReader } from './bytesReader'; +import { ClarityValue, deserializeCV, serializeCV } from './clarity'; +import { + Address, + MessageSignature, + addressFromVersionHash, + addressHashModeToVersion, +} from './common'; import { - MEMO_MAX_LENGTH_BYTES, AddressHashMode, AddressVersion, - StacksMessageType, - PostConditionPrincipalId, - PostConditionType, FungibleConditionCode, + MEMO_MAX_LENGTH_BYTES, NonFungibleConditionCode, + PostConditionPrincipalId, + PostConditionType, + StacksMessageType, } from './constants'; - -import { StacksPublicKey, serializePublicKey, deserializePublicKey, isCompressed } from './keys'; - +import { DeserializationError, SerializationError } from './errors'; import { - exceedsMaxLengthBytes, - hashP2PKH, - rightPadHexToLength, - hashP2SH, - hashP2WSH, - hashP2WPKH, -} from './utils'; - -import { BytesReader } from './bytesReader'; + StacksPublicKey, + deserializePublicKey, + publicKeyIsCompressed, + serializePublicKey, +} from './keys'; +import { Payload, deserializePayload, serializePayload } from './payload'; import { - PostCondition, - StandardPrincipal, + AssetInfo, ContractPrincipal, - PostConditionPrincipal, LengthPrefixedString, - AssetInfo, + PostCondition, + PostConditionPrincipal, + StandardPrincipal, createLPString, } from './postcondition-types'; -import { Payload, deserializePayload, serializePayload } from './payload'; -import { DeserializationError, SerializationError } from './errors'; import { - deserializeTransactionAuthField, + TransactionAuthField, deserializeMessageSignature, + deserializeTransactionAuthField, serializeMessageSignature, serializeTransactionAuthField, - TransactionAuthField, } from './signature'; import { - MessageSignature, - Address, - addressHashModeToVersion, - addressFromVersionHash, -} from './common'; -import { ClarityValue, deserializeCV, serializeCV } from './clarity'; -import { StacksNetwork, StacksNetworkName, TransactionVersion } from '@stacks/network'; + exceedsMaxLengthBytes, + hashP2PKH, + hashP2SH, + hashP2WPKH, + hashP2WSH, + rightPadHexToLength, +} from './utils'; export type StacksMessage = | Address @@ -166,8 +168,8 @@ export function addressFromPublicKeys( } if (hashMode === AddressHashMode.SerializeP2WPKH || hashMode === AddressHashMode.SerializeP2WSH) { - for (let i = 0; i < publicKeys.length; i++) { - if (!isCompressed(publicKeys[i])) { + for (const publicKey of publicKeys) { + if (!publicKeyIsCompressed(publicKey.data)) { throw Error('Public keys must be compressed for segwit'); } } diff --git a/packages/transactions/tests/authorization.test.ts b/packages/transactions/tests/authorization.test.ts index b8b8055f3..08ad32f21 100644 --- a/packages/transactions/tests/authorization.test.ts +++ b/packages/transactions/tests/authorization.test.ts @@ -16,15 +16,14 @@ import { AddressHashMode, AuthType, PubKeyEncoding } from '../src/constants'; import { bytesToHex, concatArray } from '@stacks/common'; import { BytesReader } from '../src/bytesReader'; -import { createStacksPrivateKey, createStacksPublicKey, signWithKey } from '../src/keys'; +import { createStacksPublicKey, signWithKey } from '../src/keys'; test('ECDSA recoverable signature', () => { - const privKeyString = 'edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc'; + const privKey = 'edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc'; const messagetoSign = 'eec72e6cd1ce0ac1dd1a0c260f099a8fc72498c80b3447f962fd5d39a3d70921'; const correctSignature = '019901d8b1d67a7b853dc473d0609508ab2519ec370eabfef460aa0fd9234660' + '787970968562da9de8b024a7f36f946b2fdcbf39b2f59247267a9d72730f19276b'; - const privKey = createStacksPrivateKey(privKeyString); const messageSignature = signWithKey(privKey, messagetoSign); expect(messageSignature.data).toBe(correctSignature); }); @@ -708,7 +707,7 @@ test('Single sig P2WPKH spending condition', () => { spP2WPKHCompressed.signer = '11'.repeat(20); // prettier-ignore - let spendingConditionP2WpkhCompressedBytes = [ + const spendingConditionP2WpkhCompressedBytes = [ // hash mode AddressHashMode.SerializeP2WPKH, // signer diff --git a/packages/transactions/tests/builder.test.ts b/packages/transactions/tests/builder.test.ts index 25c839278..d2bbd2561 100644 --- a/packages/transactions/tests/builder.test.ts +++ b/packages/transactions/tests/builder.test.ts @@ -26,11 +26,16 @@ import { callReadOnlyFunction, createFungiblePostCondition, createSTXPostCondition, + createStacksPublicKey, estimateFee, estimateTransaction, getContractMapEntry, getNonce, + privateKeyToPublic, + publicKeyIsCompressed, + publicKeyToHex, serializePostCondition, + serializePublicKey, } from '../src'; import { MultiSigSpendingCondition, @@ -85,12 +90,6 @@ import { PubKeyEncoding, TxRejectedReason, } from '../src/constants'; -import { - createStacksPrivateKey, - isCompressed, - pubKeyfromPrivKey, - publicKeyToString, -} from '../src/keys'; import { TokenTransferPayload, createTokenTransferPayload, serializePayload } from '../src/payload'; import { createAssetInfo } from '../src/postcondition-types'; import { createTransactionAuthField } from '../src/signature'; @@ -389,15 +388,14 @@ test('Make Multi-Sig STX token transfer', async () => { const authType = AuthType.Standard; const addressHashMode = AddressHashMode.SerializeP2SH; - const privKeyStrings = [ + const privKeys = [ '6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001', '2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01', 'd5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201', ]; - const privKeys = privKeyStrings.map(createStacksPrivateKey); - const pubKeys = privKeyStrings.map(pubKeyfromPrivKey); - const pubKeyStrings = pubKeys.map(publicKeyToString); + const pubKeys = privKeys.map(privateKeyToPublic).map(createStacksPublicKey); + const pubKeyStrings = pubKeys.map(serializePublicKey).map(publicKeyToHex); const transaction = await makeUnsignedSTXTokenTransfer({ recipient, @@ -465,15 +463,14 @@ test('Should deserialize partially signed multi-Sig STX token transfer', async ( const authType = AuthType.Standard; const addressHashMode = AddressHashMode.SerializeP2SH; - const privKeyStrings = [ + const privKeys = [ '6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001', '2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01', 'd5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201', ]; - const privKeys = privKeyStrings.map(createStacksPrivateKey); - const pubKeys = privKeyStrings.map(pubKeyfromPrivKey); - const pubKeyStrings = pubKeys.map(publicKeyToString); + const pubKeys = privKeys.map(privateKeyToPublic).map(createStacksPublicKey); + const pubKeyStrings = pubKeys.map(serializePublicKey).map(publicKeyToHex); const transaction = await makeUnsignedSTXTokenTransfer({ recipient, @@ -543,15 +540,14 @@ test('Should throw error if multisig transaction is oversigned', async () => { const nonce = 0; const memo = 'test memo'; - const privKeyStrings = [ + const privKeys = [ '6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001', '2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01', 'd5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201', ]; - const privKeys = privKeyStrings.map(createStacksPrivateKey); - const pubKeys = privKeyStrings.map(pubKeyfromPrivKey); - const pubKeyStrings = pubKeys.map(publicKeyToString); + const pubKeys = privKeys.map(privateKeyToPublic).map(createStacksPublicKey); + const pubKeyStrings = pubKeys.map(serializePublicKey).map(publicKeyToHex); const transaction = await makeUnsignedSTXTokenTransfer({ recipient, @@ -588,15 +584,14 @@ test('Make Multi-Sig STX token transfer with two transaction signers', async () const authType = AuthType.Standard; const addressHashMode = AddressHashMode.SerializeP2SH; - const privKeyStrings = [ + const privKeys = [ '6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001', '2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01', 'd5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201', ]; - const privKeys = privKeyStrings.map(createStacksPrivateKey); - const pubKeys = privKeyStrings.map(pubKeyfromPrivKey); - const pubKeyStrings = pubKeys.map(publicKeyToString); + const pubKeys = privKeys.map(privateKeyToPublic).map(createStacksPublicKey); + const pubKeyStrings = pubKeys.map(serializePublicKey).map(publicKeyToHex); const transaction = await makeUnsignedSTXTokenTransfer({ recipient, @@ -623,7 +618,7 @@ test('Make Multi-Sig STX token transfer with two transaction signers', async () const sig1 = nextSignature(signer.sigHash, authType, fee, nonce, privKeys[0]).nextSig; - const compressed1 = bytesToHex(privKeys[0].data).endsWith('01'); + const compressed1 = privKeys[0].endsWith('01'); const field1 = createTransactionAuthField( compressed1 ? PubKeyEncoding.Compressed : PubKeyEncoding.Uncompressed, sig1 @@ -644,13 +639,13 @@ test('Make Multi-Sig STX token transfer with two transaction signers', async () const sig2 = nextSignature(signer2.sigHash, authType, fee, nonce, privKeys[1]).nextSig; - const compressed2 = bytesToHex(privKeys[1].data).endsWith('01'); + const compressed2 = privKeys[1].endsWith('01'); const field2 = createTransactionAuthField( compressed2 ? PubKeyEncoding.Compressed : PubKeyEncoding.Uncompressed, sig2 ); - const compressedPub = isCompressed(pubKeys[2]); + const compressedPub = publicKeyIsCompressed(pubKeys[2].data); const field3 = createTransactionAuthField( compressedPub ? PubKeyEncoding.Compressed : PubKeyEncoding.Uncompressed, pubKeys[2] @@ -835,21 +830,21 @@ test('make a multi-sig contract deploy', async () => { const codeBody = fs.readFileSync('./tests/contracts/kv-store.clar').toString(); const fee = 0; const nonce = 0; - const privKeyStrings = [ + const privKeys = [ '6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001', '2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01', 'd5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201', ]; - const pubKeys = privKeyStrings.map(pubKeyfromPrivKey); - const pubKeyStrings = pubKeys.map(publicKeyToString); + const pubKeys = privKeys.map(privateKeyToPublic).map(createStacksPublicKey); + const pubKeyStrings = pubKeys.map(serializePublicKey).map(publicKeyToHex); const transaction = await makeContractDeploy({ codeBody, contractName, publicKeys: pubKeyStrings, numSignatures: 3, - signerKeys: privKeyStrings, + signerKeys: privKeys, fee, nonce, network: STACKS_TESTNET, @@ -1109,14 +1104,14 @@ test('make a multi-sig contract call', async () => { const functionName = 'get-value'; const buffer = bufferCV(utf8ToBytes('foo')); const fee = 0; - const privKeyStrings = [ + const privKeys = [ '6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001', '2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01', 'd5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201', ]; - const pubKeys = privKeyStrings.map(pubKeyfromPrivKey); - const pubKeyStrings = pubKeys.map(publicKeyToString); + const pubKeys = privKeys.map(privateKeyToPublic).map(createStacksPublicKey); + const pubKeyStrings = pubKeys.map(serializePublicKey).map(publicKeyToHex); const transaction = await makeContractCall({ contractAddress, @@ -1125,7 +1120,7 @@ test('make a multi-sig contract call', async () => { functionArgs: [buffer], publicKeys: pubKeyStrings, numSignatures: 3, - signerKeys: privKeyStrings, + signerKeys: privKeys, fee, nonce: 1, network: STACKS_TESTNET, @@ -1145,7 +1140,7 @@ test('Estimate transaction transfer fee', async () => { const fee = 0; const nonce = 0; const senderKey = 'edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc01'; - const publicKey = publicKeyToString(pubKeyfromPrivKey(senderKey)); + const publicKey = privateKeyToPublic(senderKey); const memo = 'test memo'; const transaction = await makeUnsignedSTXTokenTransfer({ @@ -1312,7 +1307,7 @@ test('Single-sig transaction byte length must include signature', async () => { const signer = new TransactionSigner(unsignedTransaction); // Now sign the transaction and verify the byteLength after adding signature - signer.signOrigin(createStacksPrivateKey(privateKey)); + signer.signOrigin(privateKey); const finalSerializedTx = signer.transaction.serialize(); @@ -1334,15 +1329,14 @@ test('Multi-sig transaction byte length must include the required signatures', a const nonce = 10; const memo = 'test memo...'; - const privKeyStrings = [ + const privKeys = [ '6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001', '2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01', 'd5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201', ]; - const privKeys = privKeyStrings.map(createStacksPrivateKey); - const pubKeys = privKeyStrings.map(pubKeyfromPrivKey); - const pubKeyStrings = pubKeys.map(publicKeyToString); + const pubKeys = privKeys.map(privateKeyToPublic).map(createStacksPublicKey); + const pubKeyStrings = pubKeys.map(serializePublicKey).map(publicKeyToHex); // Create a unsigned multi-sig transaction const transaction = await makeUnsignedSTXTokenTransfer({ @@ -1480,13 +1474,13 @@ test('Make sponsored STX token transfer', async () => { const payload = createTokenTransferPayload(recipient, amount, memo); const baseSpendingCondition = createSingleSigSpendingCondition( addressHashMode, - publicKeyToString(pubKeyfromPrivKey(senderKey)), + privateKeyToPublic(senderKey), nonce, fee ); const sponsorSpendingCondition = createSingleSigSpendingCondition( addressHashMode, - publicKeyToString(pubKeyfromPrivKey(sponsorKey)), + privateKeyToPublic(sponsorKey), sponsorNonce, sponsorFee ); @@ -1495,8 +1489,8 @@ test('Make sponsored STX token transfer', async () => { const sponsoredTransaction = new StacksTransaction(transactionVersion, authorization, payload); const signer = new TransactionSigner(sponsoredTransaction); - signer.signOrigin(createStacksPrivateKey(senderKey)); - signer.signSponsor(createStacksPrivateKey(sponsorKey)); + signer.signOrigin(senderKey); + signer.signSponsor(sponsorKey); // Sponsored spending condition const sponsoredTransactionClone = signer.transaction; diff --git a/packages/transactions/tests/clarity.test.ts b/packages/transactions/tests/clarity.test.ts index 1ddcee549..96321b45a 100644 --- a/packages/transactions/tests/clarity.test.ts +++ b/packages/transactions/tests/clarity.test.ts @@ -56,10 +56,10 @@ import assert from 'assert'; const ADDRESS = 'SP2JXKMSH007NPYAQHKJPQMAQYAD90NQGTVJVQ02B'; -function serializeDeserialize(value: T): ClarityValue { +function serializeDeserialize(value: T): T { const serializedDeserialized: Uint8Array = serializeCV(value); const bytesReader = new BytesReader(serializedDeserialized); - return deserializeCV(bytesReader); + return deserializeCV(bytesReader) as T; } describe('Clarity Types', () => { @@ -322,7 +322,7 @@ describe('Clarity Types', () => { expect(max128.value.toString()).toBe('340282366920938463463374607431768211455'); const serializedMax = serializeCV(max128); expect('0x' + bytesToHex(serializedMax.slice(1))).toBe('0xffffffffffffffffffffffffffffffff'); - const serializedDeserializedMax = serializeDeserialize(max128) as IntCV; + const serializedDeserializedMax = serializeDeserialize(max128); expect(cvToString(serializedDeserializedMax)).toBe(cvToString(max128)); // Min 128-bit integer @@ -330,7 +330,7 @@ describe('Clarity Types', () => { expect(min128.value.toString()).toBe('0'); const serializedMin = serializeCV(min128); expect('0x' + bytesToHex(serializedMin.slice(1))).toBe('0x00000000000000000000000000000000'); - const serializedDeserializedMin = serializeDeserialize(min128) as IntCV; + const serializedDeserializedMin = serializeDeserialize(min128); expect(cvToString(serializedDeserializedMin)).toBe(cvToString(min128)); // Out of bounds, too large @@ -436,7 +436,6 @@ describe('Clarity Types', () => { // Test lexicographic ordering of tuple keys (to match Node Buffer compare) const lexicographic = Object.keys(tuple.data).sort((a, b) => { const bufA = Buffer.from(a); - const bufB = Buffer.from(b); return bufA.compare(bufB); }); diff --git a/packages/transactions/tests/keys.test.ts b/packages/transactions/tests/keys.test.ts index f89953b8d..d47d222d8 100644 --- a/packages/transactions/tests/keys.test.ts +++ b/packages/transactions/tests/keys.test.ts @@ -2,8 +2,8 @@ import { sha256 } from '@noble/hashes/sha256'; import { getPublicKey as nobleGetPublicKey, signSync as nobleSecp256k1Sign, - utils, verify as nobleSecp256k1Verify, + utils, } from '@noble/secp256k1'; import { bytesToHex, @@ -15,24 +15,22 @@ import { } from '@stacks/common'; import { ec as EC } from 'elliptic'; import { + PubKeyEncoding, + StacksMessageType, compressPublicKey, - createStacksPrivateKey, encodeStructuredData, + createStacksPublicKey, getAddressFromPrivateKey, getAddressFromPublicKey, - getPublicKey, makeRandomPrivKey, - privateKeyToString, - PubKeyEncoding, - pubKeyfromPrivKey, + privateKeyToHex, + privateKeyToPublic, publicKeyFromSignatureRsv, publicKeyFromSignatureVrs, - publicKeyToString, + publicKeyToHex, signMessageHashRsv, signStructuredData, signWithKey, - StacksMessageType, - StacksPublicKey, stringAsciiCV, tupleCV, uintCV, @@ -45,43 +43,40 @@ import { TransactionVersion } from '@stacks/network'; // Better do it once and reuse it const ec = new EC('secp256k1'); -test('pubKeyfromPrivKey', () => { +test(privateKeyToPublic.name, () => { expect( - pubKeyfromPrivKey('edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc01').data - .byteLength - ).toBe(33); + privateKeyToPublic('edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc01').length + ).toBe(33 * 2); expect( - pubKeyfromPrivKey('edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc').data - .byteLength - ).toBe(65); + privateKeyToPublic('edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc').length + ).toBe(65 * 2); }); test('Stacks public key and private keys', () => { - const privKeyString = 'edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc'; + const privKey = 'edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc'; const pubKeyString = '04ef788b3830c00abe8f64f62dc32fc863bc0b2cafeb073b6c8e1c7657d9c2c3ab' + '5b435d20ea91337cdd8c30dd7427bb098a5355e9c9bfad43797899b8137237cf'; - const pubKey = pubKeyfromPrivKey(privKeyString); - expect(publicKeyToString(pubKey)).toBe(pubKeyString); + const pubKey = createStacksPublicKey(privateKeyToPublic(privKey)); + expect(publicKeyToHex(pubKey.data)).toBe(pubKeyString); - const deserialized = serializeDeserialize(pubKey, StacksMessageType.PublicKey) as StacksPublicKey; - expect(publicKeyToString(deserialized)).toBe(pubKeyString); + const deserialized = serializeDeserialize(pubKey, StacksMessageType.PublicKey); + expect(bytesToHex(deserialized.data)).toBe(pubKeyString); - const privKey = createStacksPrivateKey(privKeyString); - expect(publicKeyToString(getPublicKey(privKey))).toBe(pubKeyString); + expect(privateKeyToPublic(privKey)).toBe(pubKeyString); - const randomKey = makeRandomPrivKey(); - expect(privateKeyToString(randomKey).length).toEqual(64); + const randomKey = makeRandomPrivKey(); // defaults to uncompressed (i.e. no 01 suffix) + expect(privateKeyToHex(randomKey).length).toEqual(64); - expect(getAddressFromPrivateKey(privKeyString)).toBe('SPZG6BAY4JVR9RNAB1HY92B7Q208ZYY4HZEA9PX5'); - expect(getAddressFromPrivateKey(hexToBytes(privKeyString))).toBe( + expect(getAddressFromPrivateKey(privKey)).toBe('SPZG6BAY4JVR9RNAB1HY92B7Q208ZYY4HZEA9PX5'); + expect(getAddressFromPrivateKey(hexToBytes(privKey))).toBe( 'SPZG6BAY4JVR9RNAB1HY92B7Q208ZYY4HZEA9PX5' ); - expect(getAddressFromPrivateKey(privKeyString, TransactionVersion.Testnet)).toBe( + expect(getAddressFromPrivateKey(privKey, TransactionVersion.Testnet)).toBe( 'STZG6BAY4JVR9RNAB1HY92B7Q208ZYY4HZG8ZXFM' ); - expect(getAddressFromPrivateKey(hexToBytes(privKeyString), TransactionVersion.Testnet)).toBe( + expect(getAddressFromPrivateKey(hexToBytes(privKey), TransactionVersion.Testnet)).toBe( 'STZG6BAY4JVR9RNAB1HY92B7Q208ZYY4HZG8ZXFM' ); @@ -97,7 +92,7 @@ test('Stacks public key and private keys', () => { 'STZG6BAY4JVR9RNAB1HY92B7Q208ZYY4HZG8ZXFM' ); - const compressedPubKey = bytesToHex(compressPublicKey(pubKey.data).data); + const compressedPubKey = compressPublicKey(pubKey.data); expect(compressedPubKey).toBe( '03ef788b3830c00abe8f64f62dc32fc863bc0b2cafeb073b6c8e1c7657d9c2c3ab' ); @@ -112,9 +107,7 @@ test('signWithKey', () => { // ) // >> true - const privateKey = createStacksPrivateKey( - 'bcf62fdd286f9b30b2c289cce3189dbf3b502dcd955b2dc4f67d18d77f3e73c7' - ); + const privateKey = 'bcf62fdd286f9b30b2c289cce3189dbf3b502dcd955b2dc4f67d18d77f3e73c7'; const expectedMessageHash = 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e'; const expectedSignatureVrs = '00f540e429fc6e8a4c27f2782479e739cae99aa21e8cb25d4436f333577bc791cd1d9672055dd1604dd5194b88076e4f859dd93c834785ed589ec38291698d4142'; @@ -135,9 +128,7 @@ test('signMessageHashRsv', () => { // ) // >> true - const privateKey = createStacksPrivateKey( - 'bcf62fdd286f9b30b2c289cce3189dbf3b502dcd955b2dc4f67d18d77f3e73c7' - ); + const privateKey = 'bcf62fdd286f9b30b2c289cce3189dbf3b502dcd955b2dc4f67d18d77f3e73c7'; const expectedMessageHash = 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e'; const expectedSignatureRsv = 'f540e429fc6e8a4c27f2782479e739cae99aa21e8cb25d4436f333577bc791cd1d9672055dd1604dd5194b88076e4f859dd93c834785ed589ec38291698d414200'; @@ -151,9 +142,7 @@ test('signMessageHashRsv', () => { test('noble sign message', () => { // example from https://paulmillr.com/noble/ - const privateKey = createStacksPrivateKey( - '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' - ); + const privateKey = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; const expectedMessageHash = '011a775441ecb14943130a16f00cdd41818a83dd04372f3259e3ca7237e3cdaa'; const expectedR = 114926983411733245831514739773229123958640458736536797227773647312126690926912n; const expectedS = 3148023981578716756961627923124903910422344826113324160219886779423594190576n; @@ -170,9 +159,7 @@ test('noble sign message', () => { }); test('Retrieve public key from rsv signature', () => { - const privKey = createStacksPrivateKey( - 'edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc' - ); + const privKey = 'edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc'; const uncompressedPubKey = '04ef788b3830c00abe8f64f62dc32fc863bc0b2cafeb073b6c8e1c7657d9c2c3ab5b435d20ea91337cdd8c30dd7427bb098a5355e9c9bfad43797899b8137237cf'; const compressedPubKey = '03ef788b3830c00abe8f64f62dc32fc863bc0b2cafeb073b6c8e1c7657d9c2c3ab'; @@ -197,9 +184,7 @@ test('Retrieve public key from rsv signature', () => { }); test('Retrieve public key from vrs signature', () => { - const privateKey = createStacksPrivateKey( - 'edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc' - ); + const privateKey = 'edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc'; const uncompressedPubKey = '04ef788b3830c00abe8f64f62dc32fc863bc0b2cafeb073b6c8e1c7657d9c2c3ab5b435d20ea91337cdd8c30dd7427bb098a5355e9c9bfad43797899b8137237cf'; const compressedPubKey = '03ef788b3830c00abe8f64f62dc32fc863bc0b2cafeb073b6c8e1c7657d9c2c3ab'; @@ -223,9 +208,7 @@ test('Retrieve public key from vrs signature', () => { }); test('Retrieve public key from SIP-018 signature', () => { - const privKey = createStacksPrivateKey( - 'edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc' - ); + const privKey = 'edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc'; const uncompressedPubKey = '04ef788b3830c00abe8f64f62dc32fc863bc0b2cafeb073b6c8e1c7657d9c2c3ab5b435d20ea91337cdd8c30dd7427bb098a5355e9c9bfad43797899b8137237cf'; const compressedPubKey = '03ef788b3830c00abe8f64f62dc32fc863bc0b2cafeb073b6c8e1c7657d9c2c3ab'; diff --git a/packages/transactions/tests/macros.ts b/packages/transactions/tests/macros.ts index 442947e59..247d70c9a 100644 --- a/packages/transactions/tests/macros.ts +++ b/packages/transactions/tests/macros.ts @@ -2,8 +2,11 @@ import { StacksMessage, serializeStacksMessage, deserializeStacksMessage } from import { BytesReader } from '../src/bytesReader'; import { StacksMessageType } from '../src/constants'; -export function serializeDeserialize(value: StacksMessage, type: StacksMessageType): StacksMessage { +export function serializeDeserialize( + value: V, + type: T +): V { const serialized = serializeStacksMessage(value); const byteReader = new BytesReader(serialized); - return deserializeStacksMessage(byteReader, type); + return deserializeStacksMessage(byteReader, type) as V; } diff --git a/packages/transactions/tests/structuredDataSignature.test.ts b/packages/transactions/tests/structuredDataSignature.test.ts index b5649cb32..acac3d88f 100644 --- a/packages/transactions/tests/structuredDataSignature.test.ts +++ b/packages/transactions/tests/structuredDataSignature.test.ts @@ -2,7 +2,7 @@ import { sha256 } from '@noble/hashes/sha256'; import { asciiToBytes, bytesToHex, hexToBytes } from '@stacks/common'; import { verifyMessageSignatureRsv } from '@stacks/encryption'; import { standardPrincipalCV, stringAsciiCV, trueCV, tupleCV, uintCV } from '../src/clarity'; -import { createStacksPrivateKey, publicKeyFromSignatureRsv, signMessageHashRsv } from '../src/keys'; +import { publicKeyFromSignatureRsv, signMessageHashRsv } from '../src/keys'; import { decodeStructuredDataSignature, encodeStructuredData, @@ -212,7 +212,7 @@ describe('SIP018 test vectors', () => { }); test('Message signing', () => { - const privateKeyString = '753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601'; + const privateKey = '753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601'; const publicKey = '0390a5cac7c33fda49f70bc1b0866fa0ba7a9440d9de647fecb8132ceb76a94dfa'; // const address = 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM'; const domain = tupleCV({ @@ -224,7 +224,6 @@ describe('SIP018 test vectors', () => { const messageHash = '1bfdab6d4158313ce34073fbb8d6b0fc32c154d439def12247a0f44bb2225259'; const expectedSignature = '8b94e45701d857c9f1d1d70e8b2ca076045dae4920fb0160be0642a68cd78de072ab527b5c5277a593baeb2a8b657c216b99f7abb5d14af35b4bf12ba6460ba401'; - const privateKey = createStacksPrivateKey(privateKeyString); const computedSignature = signStructuredData({ message, domain, @@ -251,11 +250,11 @@ test('verifyMessageSignature works for both legacy/current and future message si const signature = signMessageHashRsv({ messageHash: encodedMessageHash, - privateKey: createStacksPrivateKey(privateKey), + privateKey, }); const signatureAlt = signMessageHashRsv({ messageHash: encodedMessageHashAlt, - privateKey: createStacksPrivateKey(privateKey), + privateKey, }); const publicKey = publicKeyFromSignatureRsv(encodedMessageHash, signature); diff --git a/packages/transactions/tests/transaction.test.ts b/packages/transactions/tests/transaction.test.ts index 3b7d2c0de..19eb5d65d 100644 --- a/packages/transactions/tests/transaction.test.ts +++ b/packages/transactions/tests/transaction.test.ts @@ -1,5 +1,6 @@ -import { deserializeTransaction, StacksTransaction } from '../src/transaction'; - +import { bytesToHex, hexToBytes } from '@stacks/common'; +import { DEFAULT_CHAIN_ID, TransactionVersion } from '@stacks/network'; +import fetchMock from 'jest-fetch-mock'; import { createMultiSigSpendingCondition, createSingleSigSpendingCondition, @@ -9,18 +10,8 @@ import { SingleSigSpendingCondition, SponsoredAuthorization, } from '../src/authorization'; - -import { - CoinbasePayloadToAltRecipient, - createTokenTransferPayload, - TokenTransferPayload, -} from '../src/payload'; - -import { createSTXPostCondition } from '../src/postcondition'; - -import { createStandardPrincipal, STXPostCondition } from '../src/postcondition-types'; -import { createLPList } from '../src/types'; - +import { BytesReader } from '../src/bytesReader'; +import { contractPrincipalCV, standardPrincipalCV } from '../src/clarity'; import { AddressHashMode, AnchorMode, @@ -28,16 +19,22 @@ import { FungibleConditionCode, PostConditionMode, } from '../src/constants'; - -import { createStacksPrivateKey, pubKeyfromPrivKey, publicKeyToString } from '../src/keys'; - +import { + createStacksPublicKey, + privateKeyToPublic, + publicKeyToHex, + serializePublicKey, +} from '../src/keys'; +import { + CoinbasePayloadToAltRecipient, + createTokenTransferPayload, + TokenTransferPayload, +} from '../src/payload'; +import { createSTXPostCondition } from '../src/postcondition'; +import { createStandardPrincipal, STXPostCondition } from '../src/postcondition-types'; import { TransactionSigner } from '../src/signer'; - -import { bytesToHex, hexToBytes } from '@stacks/common'; -import fetchMock from 'jest-fetch-mock'; -import { BytesReader } from '../src/bytesReader'; -import { contractPrincipalCV, standardPrincipalCV } from '../src/clarity'; -import { DEFAULT_CHAIN_ID, TransactionVersion } from '@stacks/network'; +import { deserializeTransaction, StacksTransaction } from '../src/transaction'; +import { createLPList } from '../src/types'; beforeEach(() => { fetchMock.resetMocks(); @@ -78,7 +75,7 @@ test('STX token transfer transaction serialization and deserialization', () => { ); const signer = new TransactionSigner(transaction); - signer.signOrigin(createStacksPrivateKey(secretKey)); + signer.signOrigin(secretKey); // const signature = // '01051521ac2ac6e6123dcaf9dba000e0005d9855bcc1bc6b96aaf8b6a385238a2317' + // 'ab21e489aca47af3288cdaebd358b0458a9159cadc314cecb7dd08043c0a6d'; @@ -156,7 +153,7 @@ test('STX token transfer transaction fee setting', () => { ); const signer = new TransactionSigner(transaction); - signer.signOrigin(createStacksPrivateKey(secretKey)); + signer.signOrigin(secretKey); // const signature = // '01051521ac2ac6e6123dcaf9dba000e0005d9855bcc1bc6b96aaf8b6a385238a2317' + // 'ab21e489aca47af3288cdaebd358b0458a9159cadc314cecb7dd08043c0a6d'; @@ -200,15 +197,14 @@ test('STX token transfer transaction multi-sig serialization and deserialization const nonce = 0; const fee = 0; - const privKeyStrings = [ + const privKeys = [ '6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e001', '2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af01', 'd5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d201', ]; - const privKeys = privKeyStrings.map(createStacksPrivateKey); - const pubKeys = privKeyStrings.map(pubKeyfromPrivKey); - const pubKeyStrings = pubKeys.map(publicKeyToString); + const pubKeys = privKeys.map(privateKeyToPublic).map(createStacksPublicKey); + const pubKeyStrings = pubKeys.map(serializePublicKey).map(publicKeyToHex); const spendingCondition = createMultiSigSpendingCondition( addressHashMode, @@ -270,15 +266,14 @@ test('STX token transfer transaction multi-sig uncompressed keys serialization a const nonce = 0; const fee = 0; - const privKeyStrings = [ + const privKeys = [ '6d430bb91222408e7706c9001cfaeb91b08c2be6d5ac95779ab52c6b431950e0', '2a584d899fed1d24e26b524f202763c8ab30260167429f157f1c119f550fa6af', 'd5200dee706ee53ae98a03fba6cf4fdcc5084c30cfa9e1b3462dcdeaa3e0f1d2', ]; - const privKeys = privKeyStrings.map(createStacksPrivateKey); - const pubKeys = privKeyStrings.map(pubKeyfromPrivKey); - const pubKeyStrings = pubKeys.map(publicKeyToString); + const pubKeys = privKeys.map(privateKeyToPublic).map(createStacksPublicKey); + const pubKeyStrings = pubKeys.map(serializePublicKey).map(publicKeyToHex); const spendingCondition = createMultiSigSpendingCondition( addressHashMode, @@ -361,8 +356,8 @@ test('Sponsored STX token transfer transaction serialization and deserialization const transaction = new StacksTransaction(transactionVersion, authorization, payload); const signer = new TransactionSigner(transaction); - signer.signOrigin(createStacksPrivateKey(secretKey)); - signer.signSponsor(createStacksPrivateKey(sponsorSecretKey)); + signer.signOrigin(secretKey); + signer.signSponsor(sponsorSecretKey); transaction.verifyOrigin();