Skip to content

Commit

Permalink
feat: properly serialize uncompressed auth fields
Browse files Browse the repository at this point in the history
  • Loading branch information
reedrosenbluth committed Mar 8, 2021
1 parent 9be2ec4 commit e259fe1
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 51 deletions.
74 changes: 52 additions & 22 deletions packages/transactions/src/authorization.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
import {
AuthType,
AddressHashMode,
AuthType,
MultiSigHashMode,
PubKeyEncoding,
RECOVERABLE_ECDSA_SIG_LENGTH_BYTES,
SingleSigHashMode,
MultiSigHashMode,
StacksMessageType,
} from './constants';

import { BufferArray, txidFromData, leftPadHex, cloneDeep } from './utils';
import { BufferArray, cloneDeep, leftPadHex, txidFromData } from './utils';

import {
addressFromPublicKeys,
deserializeLPList,
createEmptyAddress,
createLPList,
deserializeLPList,
serializeLPList,
createEmptyAddress,
} from './types';

import {
StacksPublicKey,
StacksPrivateKey,
compressPublicKey,
createStacksPublicKey,
isCompressed,
signWithKey,
getPublicKey,
serializePublicKey,
deserializePublicKey,
getPublicKey,
isCompressed,
publicKeyFromSignature,
serializePublicKey,
signWithKey,
StacksPrivateKey,
StacksPublicKey,
} from './keys';

import BigNum from 'bn.js';
import { BufferReader } from './bufferReader';
import { SerializationError, DeserializationError, SigningError } from './errors';
import { DeserializationError, SerializationError, SigningError } from './errors';

abstract class Deserializable {
abstract serialize(): Buffer;
Expand Down Expand Up @@ -81,21 +82,26 @@ export function deserializeMessageSignature(bufferReader: BufferReader): Message
}

enum AuthFieldType {
PublicKey = 0x00,
Signature = 0x02,
PublicKeyCompressed = 0x00,
PublicKeyUncompressed = 0x01,
SignatureCompressed = 0x02,
SignatureUncompressed = 0x03,
}

export type TransactionAuthFieldContents = StacksPublicKey | MessageSignature;

export interface TransactionAuthField {
type: StacksMessageType.TransactionAuthField;
pubKeyEncoding: PubKeyEncoding;
contents: TransactionAuthFieldContents;
}

export function createTransactionAuthField(
pubKeyEncoding: PubKeyEncoding,
contents: TransactionAuthFieldContents
): TransactionAuthField {
return {
pubKeyEncoding,
type: StacksMessageType.TransactionAuthField,
contents,
};
Expand All @@ -106,11 +112,20 @@ export function serializeTransactionAuthField(field: TransactionAuthField): Buff

switch (field.contents.type) {
case StacksMessageType.PublicKey:
bufferArray.appendByte(AuthFieldType.PublicKey);
bufferArray.push(serializePublicKey(field.contents));
if (field.pubKeyEncoding == PubKeyEncoding.Compressed) {
bufferArray.appendByte(AuthFieldType.PublicKeyCompressed);
bufferArray.push(serializePublicKey(field.contents));
} else {
bufferArray.appendByte(AuthFieldType.PublicKeyUncompressed);
bufferArray.push(serializePublicKey(compressPublicKey(field.contents.data)));
}
break;
case StacksMessageType.MessageSignature:
bufferArray.appendByte(AuthFieldType.Signature);
if (field.pubKeyEncoding == PubKeyEncoding.Compressed) {
bufferArray.appendByte(AuthFieldType.SignatureCompressed);
} else {
bufferArray.appendByte(AuthFieldType.SignatureUncompressed);
}
bufferArray.push(serializeMessageSignature(field.contents));
break;
}
Expand All @@ -124,10 +139,26 @@ export function deserializeTransactionAuthField(bufferReader: BufferReader): Tra
});

switch (authFieldType) {
case AuthFieldType.PublicKey:
return createTransactionAuthField(deserializePublicKey(bufferReader));
case AuthFieldType.Signature:
return createTransactionAuthField(deserializeMessageSignature(bufferReader));
case AuthFieldType.PublicKeyCompressed:
return createTransactionAuthField(
PubKeyEncoding.Compressed,
deserializePublicKey(bufferReader)
);
case AuthFieldType.PublicKeyUncompressed:
return createTransactionAuthField(
PubKeyEncoding.Uncompressed,
deserializePublicKey(bufferReader)
);
case AuthFieldType.SignatureCompressed:
return createTransactionAuthField(
PubKeyEncoding.Compressed,
deserializeMessageSignature(bufferReader)
);
case AuthFieldType.SignatureUncompressed:
return createTransactionAuthField(
PubKeyEncoding.Uncompressed,
deserializeMessageSignature(bufferReader)
);
default:
throw new Error(`Unknown auth field type: ${JSON.stringify(authFieldType)}`);
}
Expand Down Expand Up @@ -254,7 +285,6 @@ export function deserializeSingleSigSpendingCondition(
throw new DeserializationError(`Could not parse ${n} as PubKeyEncoding`);
});
const signature = deserializeMessageSignature(bufferReader);

return {
hashMode,
signer,
Expand Down
10 changes: 8 additions & 2 deletions packages/transactions/src/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ export function pubKeyfromPrivKey(privateKey: string | Buffer): StacksPublicKey
return createStacksPublicKey(pubKey);
}

export function compressPublicKey(publicKey: string | Buffer): StacksPublicKey {
const ec = new EC('secp256k1');
const key = ec.keyFromPublic(publicKey);
const pubKey = key.getPublic(true, 'hex');
return createStacksPublicKey(pubKey);
}

export function deserializePublicKey(bufferReader: BufferReader): StacksPublicKey {
const fieldId = bufferReader.readUInt8();
const keyLength =
Expand Down Expand Up @@ -156,8 +163,7 @@ export function signWithKey(privateKey: StacksPrivateKey, input: string): Messag
}
const recoveryParam = intToHexString(signature.recoveryParam, 1);
const recoverableSignatureString = recoveryParam + r + s;
const recoverableSignature = createMessageSignature(recoverableSignatureString);
return recoverableSignature;
return createMessageSignature(recoverableSignatureString);
}

export function getSignatureRecoveryParam(signature: string) {
Expand Down
41 changes: 27 additions & 14 deletions packages/transactions/src/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
import {
AnchorMode,
AuthType,
ChainID,
DEFAULT_CHAIN_ID,
TransactionVersion,
PayloadType,
AnchorMode,
PostConditionMode,
AuthType,
PubKeyEncoding,
StacksMessageType,
ChainID,
TransactionVersion,
} from './constants';

import {
Authorization,
SpendingCondition,
nextSignature,
createMessageSignature,
createTransactionAuthField,
isSingleSig,
nextSignature,
SingleSigSpendingCondition,
createTransactionAuthField,
createMessageSignature,
SpendingCondition,
} from './authorization';

import { BufferArray, txidFromData, cloneDeep } from './utils';
import { BufferArray, cloneDeep, txidFromData } from './utils';

import { Payload, serializePayload, deserializePayload } from './payload';
import { deserializePayload, Payload, serializePayload } from './payload';

import { LengthPrefixedList, serializeLPList, deserializeLPList, createLPList } from './types';
import { createLPList, deserializeLPList, LengthPrefixedList, serializeLPList } from './types';

import { StacksPrivateKey, StacksPublicKey } from './keys';
import { isCompressed, StacksPrivateKey, StacksPublicKey } from './keys';

import { BufferReader } from './bufferReader';

Expand Down Expand Up @@ -132,7 +133,13 @@ export class StacksTransaction {
appendPubkey(publicKey: StacksPublicKey) {
const cond = this.auth.spendingCondition;
if (cond && !isSingleSig(cond)) {
cond.fields.push(createTransactionAuthField(publicKey));
const compressed = isCompressed(publicKey);
cond.fields.push(
createTransactionAuthField(
compressed ? PubKeyEncoding.Compressed : PubKeyEncoding.Uncompressed,
publicKey
)
);
} else {
throw new Error(`Can't append public key to a singlesig condition`);
}
Expand All @@ -154,7 +161,13 @@ export class StacksTransaction {
if (isSingleSig(condition)) {
condition.signature = nextSig;
} else {
condition.fields.push(createTransactionAuthField(nextSig));
const compressed = privateKey.data.toString('hex').endsWith('01');
condition.fields.push(
createTransactionAuthField(
compressed ? PubKeyEncoding.Compressed : PubKeyEncoding.Uncompressed,
nextSig
)
);
}

return nextSigHash;
Expand Down
22 changes: 9 additions & 13 deletions packages/transactions/tests/authorization.test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
import {
SingleSigSpendingCondition,
createSingleSigSpendingCondition,
serializeSpendingCondition,
deserializeSpendingCondition,
createMessageSignature,
emptyMessageSignature,
createMultiSigSpendingCondition,
createSingleSigSpendingCondition,
createTransactionAuthField,
deserializeSpendingCondition,
emptyMessageSignature,
serializeSpendingCondition,
SingleSigSpendingCondition,
} from '../src/authorization';

import { AddressHashMode, PubKeyEncoding } from '../src/constants';
import {AddressHashMode, PubKeyEncoding} from '../src/constants';

import BigNum from 'bn.js';
import { BufferReader } from '../src/bufferReader';
import {
createStacksPrivateKey,
signWithKey,
createStacksPublicKey,
} from '../src/keys';
import {BufferReader} from '../src/bufferReader';
import {createStacksPrivateKey, createStacksPublicKey, signWithKey,} from '../src/keys';

test('ECDSA recoverable signature', () => {
const privKeyString = 'edf9aee84d9b7abc145504dde6726c64f369d37ee34ded868fabd876c26570bc';
Expand Down Expand Up @@ -106,7 +102,7 @@ test('Multi sig spending condition uncompressed', () => {

const signature = createMessageSignature('ff'.repeat(65));
const fields = [signature, signature, createStacksPublicKey(pubKeys[2])];
spendingCondition.fields = fields.map(createTransactionAuthField);
spendingCondition.fields = fields.map(sig => createTransactionAuthField(PubKeyEncoding.Compressed, sig));

const serializedSpendingCondition = serializeSpendingCondition(spendingCondition);

Expand Down

0 comments on commit e259fe1

Please sign in to comment.