diff --git a/packages/cashscript/src/Argument.ts b/packages/cashscript/src/Argument.ts index 915fe84d..5913e82f 100644 --- a/packages/cashscript/src/Argument.ts +++ b/packages/cashscript/src/Argument.ts @@ -59,16 +59,20 @@ export function encodeFunctionArgument(argument: FunctionArgument, typeStr: stri throw Error(`Value for type ${type} should be a Uint8Array or hex string`); } - // Redefine SIG as a bytes65 so it is included in the size checks below - // Note that ONLY Schnorr signatures are accepted + // Redefine SIG as a bytes65 (Schnorr) or bytes71, bytes72 (ECDSA) if (type === PrimitiveType.SIG && argument.byteLength !== 0) { - type = new BytesType(65); + if (![65, 71, 72].includes(argument.byteLength)) { + throw new TypeError(`bytes${argument.byteLength}`, type); + } + type = new BytesType(argument.byteLength); } - // Redefine DATASIG as a bytes64 so it is included in the size checks below - // Note that ONLY Schnorr signatures are accepted + // Redefine DATASIG as a bytes64 (Schnorr) or bytes70 (ECDSA) so it is included in the size checks below if (type === PrimitiveType.DATASIG && argument.byteLength !== 0) { - type = new BytesType(64); + if (![64, 70].includes(argument.byteLength)) { + throw new TypeError(`bytes${argument.byteLength}`, type); + } + type = new BytesType(argument.byteLength); } // Bounded bytes types require a correctly sized argument diff --git a/packages/cashscript/test/e2e/HodlVault.test.ts b/packages/cashscript/test/e2e/HodlVault.test.ts index f9ef0a92..af88689c 100644 --- a/packages/cashscript/test/e2e/HodlVault.test.ts +++ b/packages/cashscript/test/e2e/HodlVault.test.ts @@ -5,6 +5,8 @@ import { ElectrumNetworkProvider, Network, TransactionBuilder, + SignatureAlgorithm, + HashType, } from '../../src/index.js'; import { alicePriv, @@ -16,6 +18,7 @@ import { gatherUtxos, getTxOutputs } from '../test-util.js'; import { FailedRequireError } from '../../src/Errors.js'; import artifact from '../fixture/hodl_vault.artifact.js'; import { randomUtxo } from '../../src/utils.js'; +import { placeholder } from '@cashscript/utils'; describe('HodlVault', () => { const provider = process.env.TESTS_USE_MOCKNET @@ -94,5 +97,52 @@ describe('HodlVault', () => { const txOutputs = getTxOutputs(tx); expect(txOutputs).toEqual(expect.arrayContaining([{ to, amount }])); }); + + it('should succeed when price is high enough, ECDSA sig and datasig', async () => { + // given + const message = oracle.createMessage(100000n, 30000n); + const oracleSig = oracle.signMessage(message, SignatureAlgorithm.ECDSA); + const to = hodlVault.address; + const amount = 10000n; + const { utxos, changeAmount } = gatherUtxos(await hodlVault.getUtxos(), { amount, fee: 2000n }); + + const signatureTemplate = new SignatureTemplate(alicePriv, HashType.SIGHASH_ALL, SignatureAlgorithm.ECDSA); + + // when + const tx = await new TransactionBuilder({ provider }) + .addInputs(utxos, hodlVault.unlock.spend(signatureTemplate, oracleSig, message)) + .addOutput({ to: to, amount: amount }) + .addOutput({ to: to, amount: changeAmount }) + .setLocktime(100_000) + .send(); + + // then + const txOutputs = getTxOutputs(tx); + expect(txOutputs).toEqual(expect.arrayContaining([{ to, amount }])); + }); + + it('should fail to accept wrong signature lengths', async () => { + // given + const message = oracle.createMessage(100000n, 30000n); + const oracleSig = oracle.signMessage(message, SignatureAlgorithm.ECDSA); + const to = hodlVault.address; + const amount = 10000n; + const { utxos, changeAmount } = gatherUtxos(await hodlVault.getUtxos(), { amount, fee: 2000n }); + + expect(() => new TransactionBuilder({ provider }) + .addInputs(utxos, hodlVault.unlock.spend(placeholder(100), oracleSig, message)) + .addOutput({ to: to, amount: amount }) + .addOutput({ to: to, amount: changeAmount }) + .setLocktime(100_000) + .send()).toThrow("Found type 'bytes100' where type 'sig' was expected"); + + const signatureTemplate = new SignatureTemplate(alicePriv, HashType.SIGHASH_ALL, SignatureAlgorithm.ECDSA); + expect(() => new TransactionBuilder({ provider }) + .addInputs(utxos, hodlVault.unlock.spend(signatureTemplate, placeholder(100), message)) + .addOutput({ to: to, amount: amount }) + .addOutput({ to: to, amount: changeAmount }) + .setLocktime(100_000) + .send()).toThrow("Found type 'bytes100' where type 'datasig' was expected"); + }); }); }); diff --git a/packages/cashscript/test/fixture/PriceOracle.ts b/packages/cashscript/test/fixture/PriceOracle.ts index eb174099..f8643ac4 100644 --- a/packages/cashscript/test/fixture/PriceOracle.ts +++ b/packages/cashscript/test/fixture/PriceOracle.ts @@ -1,5 +1,6 @@ import { padMinimallyEncodedVmNumber, flattenBinArray, secp256k1 } from '@bitauth/libauth'; import { encodeInt, sha256 } from '@cashscript/utils'; +import { SignatureAlgorithm } from '../../src/index.js'; export class PriceOracle { constructor(public privateKey: Uint8Array) {} @@ -12,8 +13,11 @@ export class PriceOracle { return flattenBinArray([encodedBlockHeight, encodedBchUsdPrice]); } - signMessage(message: Uint8Array): Uint8Array { - const signature = secp256k1.signMessageHashSchnorr(this.privateKey, sha256(message)); + signMessage(message: Uint8Array, signatureAlgorithm: SignatureAlgorithm = SignatureAlgorithm.SCHNORR): Uint8Array { + const signature = signatureAlgorithm === SignatureAlgorithm.SCHNORR ? + secp256k1.signMessageHashSchnorr(this.privateKey, sha256(message)) : + secp256k1.signMessageHashDER(this.privateKey, sha256(message)); + if (typeof signature === 'string') throw new Error(); return signature; }