Skip to content

Commit 252ec71

Browse files
committed
Better support for ECDSA signatures in ABI arguments
1 parent 47a9dbc commit 252ec71

File tree

3 files changed

+66
-8
lines changed

3 files changed

+66
-8
lines changed

packages/cashscript/src/Argument.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,20 @@ export function encodeFunctionArgument(argument: FunctionArgument, typeStr: stri
5959
throw Error(`Value for type ${type} should be a Uint8Array or hex string`);
6060
}
6161

62-
// Redefine SIG as a bytes65 so it is included in the size checks below
63-
// Note that ONLY Schnorr signatures are accepted
62+
// Redefine SIG as a bytes65 (Schnorr) or bytes71, bytes72 (ECDSA)
6463
if (type === PrimitiveType.SIG && argument.byteLength !== 0) {
65-
type = new BytesType(65);
64+
if (![65, 71, 72].includes(argument.byteLength)) {
65+
throw new TypeError(`bytes${argument.byteLength}`, type);
66+
}
67+
type = new BytesType(argument.byteLength);
6668
}
6769

68-
// Redefine DATASIG as a bytes64 so it is included in the size checks below
69-
// Note that ONLY Schnorr signatures are accepted
70+
// Redefine DATASIG as a bytes64 (Schnorr) or bytes70 (ECDSA) so it is included in the size checks below
7071
if (type === PrimitiveType.DATASIG && argument.byteLength !== 0) {
71-
type = new BytesType(64);
72+
if (![64, 70].includes(argument.byteLength)) {
73+
throw new TypeError(`bytes${argument.byteLength}`, type);
74+
}
75+
type = new BytesType(argument.byteLength);
7276
}
7377

7478
// Bounded bytes types require a correctly sized argument

packages/cashscript/test/e2e/HodlVault.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
ElectrumNetworkProvider,
66
Network,
77
TransactionBuilder,
8+
SignatureAlgorithm,
9+
HashType,
810
} from '../../src/index.js';
911
import {
1012
alicePriv,
@@ -16,6 +18,7 @@ import { gatherUtxos, getTxOutputs } from '../test-util.js';
1618
import { FailedRequireError } from '../../src/Errors.js';
1719
import artifact from '../fixture/hodl_vault.artifact.js';
1820
import { randomUtxo } from '../../src/utils.js';
21+
import { placeholder } from '@cashscript/utils';
1922

2023
describe('HodlVault', () => {
2124
const provider = process.env.TESTS_USE_MOCKNET
@@ -94,5 +97,52 @@ describe('HodlVault', () => {
9497
const txOutputs = getTxOutputs(tx);
9598
expect(txOutputs).toEqual(expect.arrayContaining([{ to, amount }]));
9699
});
100+
101+
it('should succeed when price is high enough, ECDSA sig and datasig', async () => {
102+
// given
103+
const message = oracle.createMessage(100000n, 30000n);
104+
const oracleSig = oracle.signMessage(message, SignatureAlgorithm.ECDSA);
105+
const to = hodlVault.address;
106+
const amount = 10000n;
107+
const { utxos, changeAmount } = gatherUtxos(await hodlVault.getUtxos(), { amount, fee: 2000n });
108+
109+
const signatureTemplate = new SignatureTemplate(alicePriv, HashType.SIGHASH_ALL, SignatureAlgorithm.ECDSA);
110+
111+
// when
112+
const tx = await new TransactionBuilder({ provider })
113+
.addInputs(utxos, hodlVault.unlock.spend(signatureTemplate, oracleSig, message))
114+
.addOutput({ to: to, amount: amount })
115+
.addOutput({ to: to, amount: changeAmount })
116+
.setLocktime(100_000)
117+
.send();
118+
119+
// then
120+
const txOutputs = getTxOutputs(tx);
121+
expect(txOutputs).toEqual(expect.arrayContaining([{ to, amount }]));
122+
});
123+
124+
it('should fail to accept wrong signature lengths', async () => {
125+
// given
126+
const message = oracle.createMessage(100000n, 30000n);
127+
const oracleSig = oracle.signMessage(message, SignatureAlgorithm.ECDSA);
128+
const to = hodlVault.address;
129+
const amount = 10000n;
130+
const { utxos, changeAmount } = gatherUtxos(await hodlVault.getUtxos(), { amount, fee: 2000n });
131+
132+
expect(() => new TransactionBuilder({ provider })
133+
.addInputs(utxos, hodlVault.unlock.spend(placeholder(100), oracleSig, message))
134+
.addOutput({ to: to, amount: amount })
135+
.addOutput({ to: to, amount: changeAmount })
136+
.setLocktime(100_000)
137+
.send()).toThrow("Found type 'bytes100' where type 'sig' was expected");
138+
139+
const signatureTemplate = new SignatureTemplate(alicePriv, HashType.SIGHASH_ALL, SignatureAlgorithm.ECDSA);
140+
expect(() => new TransactionBuilder({ provider })
141+
.addInputs(utxos, hodlVault.unlock.spend(signatureTemplate, placeholder(100), message))
142+
.addOutput({ to: to, amount: amount })
143+
.addOutput({ to: to, amount: changeAmount })
144+
.setLocktime(100_000)
145+
.send()).toThrow("Found type 'bytes100' where type 'datasig' was expected");
146+
});
97147
});
98148
});

packages/cashscript/test/fixture/PriceOracle.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { padMinimallyEncodedVmNumber, flattenBinArray, secp256k1 } from '@bitauth/libauth';
22
import { encodeInt, sha256 } from '@cashscript/utils';
3+
import { SignatureAlgorithm } from '../../src/index.js';
34

45
export class PriceOracle {
56
constructor(public privateKey: Uint8Array) {}
@@ -12,8 +13,11 @@ export class PriceOracle {
1213
return flattenBinArray([encodedBlockHeight, encodedBchUsdPrice]);
1314
}
1415

15-
signMessage(message: Uint8Array): Uint8Array {
16-
const signature = secp256k1.signMessageHashSchnorr(this.privateKey, sha256(message));
16+
signMessage(message: Uint8Array, signatureAlgorithm: SignatureAlgorithm = SignatureAlgorithm.SCHNORR): Uint8Array {
17+
const signature = signatureAlgorithm === SignatureAlgorithm.SCHNORR ?
18+
secp256k1.signMessageHashSchnorr(this.privateKey, sha256(message)) :
19+
secp256k1.signMessageHashDER(this.privateKey, sha256(message));
20+
1721
if (typeof signature === 'string') throw new Error();
1822
return signature;
1923
}

0 commit comments

Comments
 (0)