Skip to content

Commit 3d97005

Browse files
committed
Add signMessageHash method to SignatureTemplate
1 parent 338fc44 commit 3d97005

File tree

7 files changed

+60
-25
lines changed

7 files changed

+60
-25
lines changed

examples/PriceOracle.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { padMinimallyEncodedVmNumber, flattenBinArray, secp256k1 } from '@bitauth/libauth';
1+
import { padMinimallyEncodedVmNumber, flattenBinArray } from '@bitauth/libauth';
22
import { encodeInt, sha256 } from '@cashscript/utils';
3+
import { SignatureAlgorithm, SignatureTemplate } from 'cashscript';
34

45
export class PriceOracle {
5-
constructor(public privateKey: Uint8Array) {}
6+
constructor(public privateKey: Uint8Array) { }
67

78
// Encode a blockHeight and bchUsdPrice into a byte sequence of 8 bytes (4 bytes per value)
89
createMessage(blockHeight: bigint, bchUsdPrice: bigint): Uint8Array {
@@ -12,9 +13,8 @@ export class PriceOracle {
1213
return flattenBinArray([encodedBlockHeight, encodedBchUsdPrice]);
1314
}
1415

15-
signMessage(message: Uint8Array): Uint8Array {
16-
const signature = secp256k1.signMessageHashSchnorr(this.privateKey, sha256(message));
17-
if (typeof signature === 'string') throw new Error();
18-
return signature;
16+
signMessage(message: Uint8Array, signatureAlgorithm: SignatureAlgorithm = SignatureAlgorithm.SCHNORR): Uint8Array {
17+
const signatureTemplate = new SignatureTemplate(this.privateKey, undefined, signatureAlgorithm);
18+
return signatureTemplate.signMessageHash(sha256(message));
1919
}
2020
}

packages/cashscript/src/SignatureTemplate.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,17 @@ export default class SignatureTemplate {
3131
}
3232
}
3333

34-
// TODO: Allow signing of non-transaction messages (i.e. don't add the hashtype)
3534
generateSignature(payload: Uint8Array, bchForkId?: boolean): Uint8Array {
35+
const signature = this.signMessageHash(payload);
36+
return Uint8Array.from([...signature, this.getHashType(bchForkId)]);
37+
}
38+
39+
signMessageHash(payload: Uint8Array): Uint8Array {
3640
const signature = this.signatureAlgorithm === SignatureAlgorithm.SCHNORR
3741
? secp256k1.signMessageHashSchnorr(this.privateKey, payload) as Uint8Array
3842
: secp256k1.signMessageHashDER(this.privateKey, payload) as Uint8Array;
3943

40-
return Uint8Array.from([...signature, this.getHashType(bchForkId)]);
44+
return signature;
4145
}
4246

4347
getHashType(bchForkId: boolean = true): number {

packages/cashscript/test/SignatureTemplate.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,22 @@ describe('SignatureTemplate', () => {
4646
});
4747
});
4848

49+
describe('signMessageHash', () => {
50+
it('should generate a correct signature using Schnorr', () => {
51+
const signatureTemplate = new SignatureTemplate(alicePriv);
52+
const signature = signatureTemplate.signMessageHash(hexToBin('0000000000000000000000'));
53+
expect(signature).toEqual(hexToBin('bcac180e17de108003cce026708bd2af54b860dad2626cee157f4ed5abd993b9085d615015f905978adc51e8878226280ddd27d899f086519c0978e53332d799'));
54+
});
55+
});
56+
57+
describe('signMessageHash', () => {
58+
it('should generate a correct signature using ECDSA', () => {
59+
const signatureTemplate = new SignatureTemplate(alicePriv, undefined, SignatureAlgorithm.ECDSA);
60+
const signature = signatureTemplate.signMessageHash(hexToBin('0000000000000000000000'));
61+
expect(signature).toEqual(hexToBin('3045022100fa1d6a159a124e99479f78152422d55ff3c16f7fac5ae47fa291907f8f47613f02200d6c906f667b3712860b6f5a1f296ecb7dcd44da83c6a1eb45869b61c6b8dadb'));
62+
});
63+
});
64+
4965
describe('getPublicKey', () => {
5066
it('should generate a correct public key', () => {
5167
const signatureTemplate = new SignatureTemplate(alicePriv);
Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { padMinimallyEncodedVmNumber, flattenBinArray, secp256k1 } from '@bitauth/libauth';
1+
import { padMinimallyEncodedVmNumber, flattenBinArray } from '@bitauth/libauth';
22
import { encodeInt, sha256 } from '@cashscript/utils';
3-
import { SignatureAlgorithm } from '../../src/index.js';
3+
import { SignatureAlgorithm, SignatureTemplate } from '../../src/index.js';
44

55
export class PriceOracle {
6-
constructor(public privateKey: Uint8Array) {}
6+
constructor(public privateKey: Uint8Array) { }
77

88
// Encode a blockHeight and bchUsdPrice into a byte sequence of 8 bytes (4 bytes per value)
99
createMessage(blockHeight: bigint, bchUsdPrice: bigint): Uint8Array {
@@ -14,11 +14,7 @@ export class PriceOracle {
1414
}
1515

1616
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-
21-
if (typeof signature === 'string') throw new Error();
22-
return signature;
17+
const signatureTemplate = new SignatureTemplate(this.privateKey, undefined, signatureAlgorithm);
18+
return signatureTemplate.signMessageHash(sha256(message));
2319
}
2420
}

website/docs/releases/release-notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ title: Release Notes
1111
- :boom: **BREAKING**: No longer seed the MockNetworkProvider with any test UTXOs.
1212
- :sparkles: Add a configurable `vmTarget` option to `MockNetworkProvider`.
1313
- :sparkles: Add support for ECDSA signatures in contract unlockers for `sig` and `datasig` parameters.
14+
- :sparkles: Add `signMessageHash()` method to `SignatureTemplate` to allow for signing of non-transaction messages.
1415
- :hammer_and_wrench: Improve libauth template generation.
1516
- :bug: Fix bug where `SignatureTemplate` would not accept private key hex strings as a signer.
1617

website/docs/sdk/examples.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,12 @@ console.log('contract balance:', await contract.getBalance());
9292
We need the create the functionality for generating and signing the oracle message to use in the HodlVault contract:
9393

9494
```ts title="PriceOracle.ts"
95-
import { padMinimallyEncodedVmNumber, flattenBinArray, secp256k1 } from '@bitauth/libauth';
95+
import { padMinimallyEncodedVmNumber, flattenBinArray } from '@bitauth/libauth';
9696
import { encodeInt, sha256 } from '@cashscript/utils';
97+
import { SignatureAlgorithm, SignatureTemplate } from 'cashscript';
9798

9899
export class PriceOracle {
99-
constructor(public privateKey: Uint8Array) {}
100+
constructor(public privateKey: Uint8Array) { }
100101

101102
// Encode a blockHeight and bchUsdPrice into a byte sequence of 8 bytes (4 bytes per value)
102103
createMessage(blockHeight: bigint, bchUsdPrice: bigint): Uint8Array {
@@ -106,12 +107,12 @@ export class PriceOracle {
106107
return flattenBinArray([encodedBlockHeight, encodedBchUsdPrice]);
107108
}
108109

109-
signMessage(message: Uint8Array): Uint8Array {
110-
const signature = secp256k1.signMessageHashSchnorr(this.privateKey, sha256(message));
111-
if (typeof signature === 'string') throw new Error();
112-
return signature;
110+
signMessage(message: Uint8Array, signatureAlgorithm: SignatureAlgorithm = SignatureAlgorithm.SCHNORR): Uint8Array {
111+
const signatureTemplate = new SignatureTemplate(this.privateKey, undefined, signatureAlgorithm);
112+
return signatureTemplate.signMessageHash(sha256(message));
113113
}
114114
}
115+
115116
```
116117

117118
### Sending a Transaction

website/docs/sdk/signature-templates.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: Signature Templates
33
---
44

5-
When a contract function has a `sig` parameter, it needs a cryptographic signature from a private key for the spending transaction.
5+
When a contract function has a `sig` parameter, it needs a cryptographic signature from a private key for the spending transaction.
66
In place of a signature, a `SignatureTemplate` can be passed, which will generate the correct signature when the transaction is built.
77

88
:::tip
@@ -59,7 +59,7 @@ transactionBuilder.addInput(aliceUtxos[0], aliceTemplate.unlockP2PKH());
5959

6060
### getPublicKey()
6161

62-
The `SignatureTemplate` also had a helper method to get the matching PublicKey in the following way:
62+
The `SignatureTemplate` also has a helper method to get the matching PublicKey in the following way:
6363

6464
```ts
6565
signatureTemplate.getPublicKey(): Uint8Array
@@ -72,6 +72,23 @@ import { aliceTemplate } from './somewhere.js';
7272
const alicePublicKey = aliceTemplate.getPublicKey()
7373
```
7474

75+
### signMessageHash()
76+
77+
The `SignatureTemplate` also has a helper method to sign a message hash, which can be used to sign non-transaction messages. This is useful for generating `datasig` signatures for smart contract use cases.
78+
79+
```ts
80+
signatureTemplate.signMessageHash(message: Uint8Array): Uint8Array
81+
```
82+
83+
#### Example
84+
```ts
85+
import { aliceTemplate } from './somewhere.js';
86+
import { sha256 } from '@cashscript/utils';
87+
import { hexToBin } from '@bitauth/libauth';
88+
89+
const signature = aliceTemplate.signMessageHash(sha256(hexToBin('0000000000000000000000')));
90+
```
91+
7592
## Advanced Usage
7693

7794
### HashType

0 commit comments

Comments
 (0)