import { Buffer } from '@stacks/common' ; import { AddressHashMode , AddressVersion , COMPRESSED_PUBKEY_LENGTH_BYTES , UNCOMPRESSED_PUBKEY_LENGTH_BYTES , StacksMessageType , TransactionVersion , PubKeyEncoding , } from './constants' ; import { BufferArray , hash160 , hashP2PKH , hexStringToInt , intToHexString , leftPadHexToLength , randomBytes , } from './utils' ; import { ec as EC } from 'elliptic' ; import { MessageSignature , createMessageSignature , addressHashModeToVersion , addressFromVersionHash , addressToString , } from './common' ; import { BufferReader } from './bufferReader' ; import { c32address } from 'c32check' ; export interface StacksPublicKey { readonly type : StacksMessageType . PublicKey ; readonly data : Buffer ; } /** Creates a P2PKH address string from the given private key and tx version. */ export function getAddressFromPrivateKey ( /** Private key buffer or hex string */ privateKey , transactionVersion = TransactionVersion . Mainnet ) { const pubKey = pubKeyfromPrivKey ( privateKey ) ; return getAddressFromPublicKey ( pubKey . data , transactionVersion ) ; } /** Creates a P2PKH address string from the given public key and tx version. */ export function getAddressFromPublicKey ( /** Public key buffer or hex string */ publicKey , transactionVersion = TransactionVersion . Mainnet ) { publicKey = typeof publicKey === 'string' ? publicKey : publicKey . toString ( 'hex' ) ; const addrVer = addressHashModeToVersion ( AddressHashMode . SerializeP2PKH , transactionVersion ) ; const addr = addressFromVersionHash ( addrVer , hashP2PKH ( Buffer . from ( publicKey , 'hex' ) ) ) ; const addrString = addressToString ( addr ) ; return addrString ; } export function createStacksPublicKey ( key ) { return { type : StacksMessageType . PublicKey , data : Buffer . from ( key , 'hex' ) , } ; } export function publicKeyFromSignature ( message , messageSignature , pubKeyEncoding = PubKeyEncoding . Compressed ) { const ec = new EC ( 'secp256k1' ) ; const messageBN = ec . keyFromPrivate ( message , 'hex' ) . getPrivate ( ) . toString ( 10 ) ; const parsedSignature = parseRecoverableSignature ( messageSignature . data ) ; const publicKey = ec . recoverPubKey ( messageBN , parsedSignature , parsedSignature . recoveryParam , 'hex' ) ; if ( pubKeyEncoding == PubKeyEncoding . Uncompressed ) { return publicKey . encode ( 'hex' ) ; } return publicKey . encodeCompressed ( 'hex' ) ; } export function publicKeyFromBuffer ( data ) { return { type : StacksMessageType . PublicKey , data } ; } export function isCompressed ( key ) { return ! key . data . toString ( 'hex' ) . startsWith ( '04' ) ; } export function publicKeyToString ( key ) { return key . data . toString ( 'hex' ) ; } export function serializePublicKey ( key ) { const bufferArray = new BufferArray ( ) ; bufferArray . push ( key . data ) ; return bufferArray . concatBuffer ( ) ; } export function pubKeyfromPrivKey ( privateKey ) { const privKey = createStacksPrivateKey ( privateKey ) ; const ec = new EC ( 'secp256k1' ) ; const keyPair = ec . keyFromPrivate ( privKey . data . toString ( 'hex' ) . slice ( 0 , 64 ) , 'hex' ) ; const pubKey = keyPair . getPublic ( privKey . compressed , 'hex' ) ; return createStacksPublicKey ( pubKey ) ; } export function compressPublicKey ( publicKey ) { const ec = new EC ( 'secp256k1' ) ; const key = ec . keyFromPublic ( publicKey ) ; const pubKey = key . getPublic ( true , 'hex' ) ; return createStacksPublicKey ( pubKey ) ; } export function deserializePublicKey ( bufferReader ) { const fieldId = bufferReader . readUInt8 ( ) ; const keyLength = fieldId !== 4 ? COMPRESSED_PUBKEY_LENGTH_BYTES : UNCOMPRESSED_PUBKEY_LENGTH_BYTES ; return publicKeyFromBuffer ( Buffer . concat ( [ Buffer . from ( [ fieldId ] ) , bufferReader . readBuffer ( keyLength ) ] ) ) ; } export interface StacksPrivateKey { data : Buffer ; compressed : boolean ; } export function createStacksPrivateKey ( key ) { const data = typeof key === 'string' ? Buffer . from ( key , 'hex' ) : key ; let compressed ; if ( data . length === 33 ) { if ( data [ data . length - 1 ] !== 1 ) { throw new Error ( 'Improperly formatted private-key. 33 byte length usually ' + 'indicates compressed key, but last byte must be == 0x01' ) ; } compressed = true ; } else if ( data . length === 32 ) { compressed = false ; } else { throw new Error ( `Improperly formatted private-key hex string: length should be 32 or 33 bytes, provided with length ${ data . length }` ) ; } return { data , compressed } ; } export function makeRandomPrivKey ( entropy ? ) { const ec = new EC ( 'secp256k1' ) ; const options = { entropy : entropy || randomBytes ( 32 ) } ; const keyPair = ec . genKeyPair ( options ) ; const privateKey = keyPair . getPrivate ( ) . toString ( 'hex' , 32 ) ; return createStacksPrivateKey ( privateKey ) ; } export function signWithKey ( privateKey , input ) { const ec = new EC ( 'secp256k1' ) ; const key = ec . keyFromPrivate ( privateKey . data . toString ( 'hex' ) . slice ( 0 , 64 ) , 'hex' ) ; const signature = key . sign ( input , 'hex' , { canonical : true } ) ; const coordinateValueBytes = 32 ; const r = leftPadHexToLength ( signature . r . toString ( 'hex' ) , coordinateValueBytes * 2 ) ; const s = leftPadHexToLength ( signature . s . toString ( 'hex' ) , coordinateValueBytes * 2 ) ; if ( signature . recoveryParam === undefined || signature . recoveryParam === null ) { throw new Error ( '"signature.recoveryParam" is not set' ) ; } const recoveryParam = intToHexString ( signature . recoveryParam , 1 ) ; const recoverableSignatureString = recoveryParam + r + s ; return createMessageSignature ( recoverableSignatureString ) ; } export function getSignatureRecoveryParam ( signature ) { const coordinateValueBytes = 32 ; if ( signature . length < coordinateValueBytes * 2 * 2 + 1 ) { throw new Error ( 'Invalid signature' ) ; } const recoveryParamHex = signature . substr ( 0 , 2 ) ; return hexStringToInt ( recoveryParamHex ) ; } export function parseRecoverableSignature ( signature ) { const coordinateValueBytes = 32 ; if ( signature . length < coordinateValueBytes * 2 * 2 + 1 ) { throw new Error ( 'Invalid signature' ) ; } const recoveryParamHex = signature . substr ( 0 , 2 ) ; const r = signature . substr ( 2 , coordinateValueBytes * 2 ) ; const s = signature . substr ( 2 + coordinateValueBytes * 2 , coordinateValueBytes * 2 ) ; return { recoveryParam : hexStringToInt ( recoveryParamHex ) , r , s , } ; } export function getPublicKey ( privateKey ) { return pubKeyfromPrivKey ( privateKey . data ) ; } export function privateKeyToString ( privateKey ) { return privateKey . data . toString ( 'hex' ) ; } export function publicKeyToAddress ( version , publicKey ) { return c32address ( version , hash160 ( publicKey . data ) . toString ( 'hex' ) ) ; }