Skip to content

Commit

Permalink
feat: improve authentication template validation, improve types
Browse files Browse the repository at this point in the history
  • Loading branch information
bitjson committed Apr 29, 2020
1 parent 5fe25c9 commit 9fbec21
Show file tree
Hide file tree
Showing 24 changed files with 1,946 additions and 220 deletions.
2 changes: 2 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"bitauth",
"bitcoincash",
"bitcore",
"bitfield",
"bitflags",
"BOOLAND",
"BOOLOR",
Expand Down Expand Up @@ -113,6 +114,7 @@
"typeof",
"Uint",
"uncompress",
"unintuitive",
"untrusted",
"utxo",
"VERIF",
Expand Down
7 changes: 4 additions & 3 deletions src/lib/format/numbers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Encode a positive integer as a little-endian Uint8Array. For values exceeding
* `Number.MAX_SAFE_INTEGER`, use `bigIntToBinUintLE`. Negative values will
* return the same result as `0`.
* `Number.MAX_SAFE_INTEGER` (`9007199254740991`), use `bigIntToBinUintLE`.
* Negative values will return the same result as `0`.
*
* @param value - the number to encode
*/
Expand Down Expand Up @@ -216,7 +216,8 @@ export const numberToBinInt32TwosCompliment = (value: number) => {

/**
* Decode a little-endian Uint8Array of any length into a number. For numbers
* larger than `Number.MAX_SAFE_INTEGER`, use `binToBigIntUintLE`.
* larger than `Number.MAX_SAFE_INTEGER` (`9007199254740991`), use
* `binToBigIntUintLE`.
*
* The `bytes` parameter can be set to constrain the expected length (default:
* `bin.length`). This method throws if `bin.length` is not equal to `bytes`.
Expand Down
255 changes: 222 additions & 33 deletions src/lib/template/bitauth-authentication-template.schema.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ test(
errors: [
{
error:
'Could not generate "owner.public_key" – the path "M/0\'/0" could not be derived for entity "ownerEntityId": HD key derivation error: derivation for hardened child indexes (indexes greater than or equal to 2147483648) requires an HD private node.',
'Could not generate owner.public_key – the path "M/0\'/i" is not a valid "publicDerivationPath".',
range: {
endColumn: 18,
endLineNumber: 1,
Expand Down
27 changes: 26 additions & 1 deletion src/lib/template/compiler-defaults.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
export enum CompilerDefaults {
/**
* If unset, `variable.current_block_height` is set to `2` in each scenario.
* This is the height of the second mined block after the genesis block:
* `000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd`.
*
* This default value was chosen to be low enough to simplify the debugging of
* block height offsets while remaining differentiated from `0` and `1` which
* are used both as boolean return values and for control flow.
*/
defaultScenarioCurrentBlockHeight = 2,
/**
* If unset, `variable.current_block_time` is set to `1231469665` in each
* scenario. This is the Median Time-Past block time (BIP113) of block `2`
* (the block used in `defaultScenarioCurrentBlockHeight`).
*/
defaultScenarioCurrentBlockTime = 1231469665,
/**
* If unset, each `HdKey` uses this `addressOffset`.
*/
hdKeyAddressOffset = 0,
hdKeyPrivateDerivationPath = 'm/i',
/**
* If unset, each `HdKey` uses this `hdPublicKeyDerivationPath`.
*/
hdKeyHdPublicKeyDerivationPath = 'm',
/**
* If unset, each `HdKey` uses this `privateDerivationPath`.
*/
hdKeyPrivateDerivationPath = 'm/i',
}
10 changes: 10 additions & 0 deletions src/lib/template/compiler-operation-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export const compilerOperationAttemptBytecodeResolution = compilerOperationRequi
}
);

// eslint-disable-next-line complexity
export const compilerOperationHelperDeriveHdPrivateNode = ({
addressIndex,
entityId,
Expand All @@ -167,6 +168,15 @@ export const compilerOperationHelperDeriveHdPrivateNode = ({
const privateDerivationPath =
hdKey.privateDerivationPath ?? CompilerDefaults.hdKeyPrivateDerivationPath;
const i = addressIndex + addressOffset;

const validPrivatePathWithIndex = /^m(?:\/(?:[0-9]+|i)'?)*$/u;
if (!validPrivatePathWithIndex.test(privateDerivationPath)) {
return {
error: `Could not generate ${identifier} – the path "${privateDerivationPath}" is not a valid "privateDerivationPath".`,
status: 'error',
};
}

const instancePath = privateDerivationPath.replace('i', i.toString());

const masterContents = decodeHdPrivateKey(environment, entityHdPrivateKey);
Expand Down
8 changes: 8 additions & 0 deletions src/lib/template/compiler-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,14 @@ export const compilerOperationHdKeyPublicKeyCommon = attemptCompilerOperations(
const publicDerivationPath =
hdKey.publicDerivationPath ?? privateDerivationPath.replace('m', 'M');

const validPublicPathWithIndex = /^M(?:\/(?:[0-9]+|i))*$/u;
if (!validPublicPathWithIndex.test(publicDerivationPath)) {
return {
error: `Could not generate ${identifier} – the path "${publicDerivationPath}" is not a valid "publicDerivationPath".`,
status: 'error',
};
}

const i = addressIndex + addressOffset;
const instancePath = publicDerivationPath.replace('i', i.toString());

Expand Down
17 changes: 8 additions & 9 deletions src/lib/template/compiler-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,14 +522,15 @@ export interface CompilationData<
[fullIdentifier: string]: Uint8Array;
};
/**
* The current block height at compile time.
* The current block height at address creation time.
*/
currentBlockHeight?: number;
/**
* The current block time at compile time. Note: this is not a current
* timestamp, but the median timestamp of the last 11 blocks.
* The current block time at address creation time.
*
* This value only changes when a new block is found. See BIP113 for details.
* Note, this is never a current timestamp, but rather the median timestamp of
* the last 11 blocks. This value only changes when a new block is found. See
* BIP113 for details.
*/
currentBlockTime?: Date;
/**
Expand All @@ -543,10 +544,8 @@ export interface CompilationData<
* the dynamic index (`i`) used in each `privateDerivationPath` or
* `publicDerivationPath`.
*
* This is required for any compiler operation which requires derivation
* (all operations except `public_key`s provided in `derivedPublicKeys` or
* signatures provided in `signatures`). Typically, the value is incremented
* by one for each address in a wallet.
* This is required for any compiler operation which requires derivation.
* Typically, the value is incremented by one for each address in a wallet.
*/
addressIndex?: number;
/**
Expand All @@ -560,7 +559,7 @@ export interface CompilationData<
*
* If both an HD private key (in `hdPrivateKeys`) and HD public key (in
* `hdPublicKeys`) are provided for the same entity in the same compilation
* (not recommended), only the HD private key is used.
* (not recommended), the HD private key is used.
*/
hdPublicKeys?: {
[entityId: string]: string;
Expand Down
16 changes: 8 additions & 8 deletions src/lib/template/compiler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,22 @@ test('authenticationTemplateToCompilationEnvironment: authenticationTemplateP2pk
environment,
{
entityOwnership: {
owner: 'owner',
key: 'owner',
},
lockingScriptTypes: {
lock: 'standard',
},
scripts: {
lock:
'OP_DUP\nOP_HASH160 <$(<owner.public_key> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG',
unlock: '<owner.schnorr_signature.all_outputs>\n<owner.public_key>',
'OP_DUP\nOP_HASH160 <$(<key.public_key> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG',
unlock: '<key.schnorr_signature.all_outputs>\n<key.public_key>',
},
unlockingScriptTimeLockTypes: {},
unlockingScripts: {
unlock: 'lock',
},
variables: {
owner: {
key: {
description: 'The private key which controls this wallet.',
name: 'Key',
type: 'Key',
Expand All @@ -76,22 +76,22 @@ test('authenticationTemplateToCompilationEnvironment: authenticationTemplateP2pk
environment,
{
entityOwnership: {
owner: 'owner',
key: 'owner',
},
lockingScriptTypes: {
lock: 'standard',
},
scripts: {
lock:
'OP_DUP\nOP_HASH160 <$(<owner.public_key> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG',
unlock: '<owner.schnorr_signature.all_outputs>\n<owner.public_key>',
'OP_DUP\nOP_HASH160 <$(<key.public_key> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG',
unlock: '<key.schnorr_signature.all_outputs>\n<key.public_key>',
},
unlockingScriptTimeLockTypes: {},
unlockingScripts: {
unlock: 'lock',
},
variables: {
owner: {
key: {
description: 'The private key which controls this wallet.',
name: 'Key',
type: 'HdKey',
Expand Down
2 changes: 1 addition & 1 deletion src/lib/template/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { AuthenticationTemplate } from './template-types';
* compiler
*/
export const createCompiler = <
TransactionContext extends { locktime: number },
TransactionContext extends { locktime: number; sequenceNumber: number },
Environment extends AnyCompilationEnvironment<TransactionContext>,
ProgramState = StackState & MinimumProgramState
>(
Expand Down
26 changes: 25 additions & 1 deletion src/lib/template/language/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,16 +207,23 @@ export const compileScriptP2shUnlocking = ({
/**
* Parse, resolve, and reduce the provided BTL script using the provided `data`
* and `environment`.
*
* Note, locktime validation only occurs if `transactionContext` is provided in
* the environment.
*/
// eslint-disable-next-line complexity
export const compileScript = <
ProgramState = StackState & MinimumProgramState,
TransactionContext extends { locktime: number } = { locktime: number }
TransactionContext extends { locktime: number; sequenceNumber: number } = {
locktime: number;
sequenceNumber: number;
}
>(
scriptId: string,
data: CompilationData<TransactionContext>,
environment: CompilationEnvironment<TransactionContext>
): CompilationResult<ProgramState> => {
const locktimeDisablingSequenceNumber = 0xffffffff;
const lockTimeTypeBecomesTimestamp = 500000000;
if (data.transactionContext?.locktime !== undefined) {
if (
Expand Down Expand Up @@ -251,6 +258,23 @@ export const compileScript = <
}
}

if (
data.transactionContext?.sequenceNumber !== undefined &&
environment.unlockingScriptTimeLockTypes?.[scriptId] !== undefined &&
data.transactionContext.sequenceNumber === locktimeDisablingSequenceNumber
) {
return {
errorType: 'parse',
errors: [
{
error: `The script "${scriptId}" requires a locktime, but this input's sequence number is set to disable transaction locktime (0xffffffff). This will cause the OP_CHECKLOCKTIMEVERIFY operation to error when the transaction is verified. To be valid, this input must use a sequence number which does not disable locktime.`,
range: emptyRange(),
},
],
success: false,
};
}

const rawResult = compileScriptRaw<ProgramState, TransactionContext>({
data,
environment,
Expand Down
12 changes: 6 additions & 6 deletions src/lib/template/standard/p2pkh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const authenticationTemplateP2pkhNonHd: AuthenticationTemplate = {
name: 'Owner',
scripts: ['lock', 'unlock'],
variables: {
owner: {
key: {
description: 'The private key which controls this wallet.',
name: 'Key',
type: 'Key',
Expand All @@ -35,11 +35,11 @@ export const authenticationTemplateP2pkhNonHd: AuthenticationTemplate = {
lockingType: 'standard',
name: 'P2PKH Lock',
script:
'OP_DUP\nOP_HASH160 <$(<owner.public_key> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG',
'OP_DUP\nOP_HASH160 <$(<key.public_key> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG',
},
unlock: {
name: 'Unlock',
script: '<owner.schnorr_signature.all_outputs>\n<owner.public_key>',
script: '<key.schnorr_signature.all_outputs>\n<key.public_key>',
unlocks: 'lock',
},
},
Expand Down Expand Up @@ -74,7 +74,7 @@ export const authenticationTemplateP2pkh: AuthenticationTemplate = {
name: 'Owner',
scripts: ['lock', 'unlock'],
variables: {
owner: {
key: {
description: 'The private key which controls this wallet.',
name: 'Key',
type: 'HdKey',
Expand All @@ -88,11 +88,11 @@ export const authenticationTemplateP2pkh: AuthenticationTemplate = {
lockingType: 'standard',
name: 'P2PKH Lock',
script:
'OP_DUP\nOP_HASH160 <$(<owner.public_key> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG',
'OP_DUP\nOP_HASH160 <$(<key.public_key> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG',
},
unlock: {
name: 'Unlock',
script: '<owner.schnorr_signature.all_outputs>\n<owner.public_key>',
script: '<key.schnorr_signature.all_outputs>\n<key.public_key>',
unlocks: 'lock',
},
},
Expand Down
Loading

0 comments on commit 9fbec21

Please sign in to comment.