-
Notifications
You must be signed in to change notification settings - Fork 23
/
signed.ts
104 lines (96 loc) · 3.17 KB
/
signed.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import {
base64ToBin,
binToBase64,
encodeCashAddress,
hexToBin,
instantiateSecp256k1,
instantiateSha256,
RecoveryId,
utf8ToBin,
} from "@bitauth/libauth";
import { derivePrefix } from "../util/derivePublicKeyHash";
import { hash160 } from "../util/hash160";
/**
* message_magic - "Magic" per standard bitcoin message signing.
*
* In this case, the magic is simply adding "b'24' + Bitcoin Signed Message\n" followed
* by the size of the message in binary and the message encoded as binary.
*
* @param {str} string The string to add the magic syntax to.
*
* @returns a promise to the string as binary array with magic syntax
*/
// see https://github.com/Electron-Cash/Electron-Cash/blob/49f9f672364f50053a026e4a5cb30e92db2d195d/electroncash/bitcoin.py#L524
function message_magic(str: string) {
const length = utf8ToBin(str).length.toString(16);
let payload = `\x18Bitcoin Signed Message:\n`;
return new Uint8Array([
...utf8ToBin(payload),
...hexToBin(length),
...utf8ToBin(str),
]);
}
/**
* hash_magic - Return the hash of the string that has undergone standard formatting
*
* @param {str} string The string to hash
*
* @returns a promise to the hash of the string.
*/
export async function hash_magic(str: string) {
const h = (await instantiateSha256()).hash;
return h(h(message_magic(str)));
}
export class SignedMessage {
/**
* sign - Calculate the recoverable signed checksum of a string message.
*
* @param {message} string The
* @param {privateKey} Uint8Array The private key to sign the message with
*
* @returns a promise to signature as a string
*/
public static async sign(
message: string,
privateKey: Uint8Array
): Promise<string> {
const secp256k1 = await instantiateSecp256k1();
let messageHash = await hash_magic(message);
let rs = secp256k1.signMessageHashRecoverableCompact(
privateKey,
messageHash
);
return binToBase64(rs.signature);
}
/**
* verify - Validate that the message is valid
*
* @param {message} string The message to verify as a utf8 string
* @param {signature} string The signature as a base64 encoded string
* @param {cashaddr} string The cashaddr to validate the signature against.
*
* @returns a promise to signature as a string
*/
public static async verify(
message: string,
signature: string,
cashaddr: string
): Promise<boolean> {
// Check that the signature is valid for the given message.
const secp256k1 = await instantiateSecp256k1();
let messageHash = await hash_magic(message);
let sig = base64ToBin(signature);
let prefix = derivePrefix(cashaddr);
let recoveryId = prefix === "bitcoincash" ? 0 : (1 as RecoveryId);
let pk = secp256k1.recoverPublicKeyCompressed(sig, recoveryId, messageHash);
let pkh = await hash160(pk);
let valid = secp256k1.verifySignatureCompact(sig, pk, messageHash);
// Validate that the signature actually matches the provided cashaddr
let resultingCashaddr = encodeCashAddress(prefix, 0, pkh);
if (resultingCashaddr !== cashaddr) {
console.log("cashaddr match failed");
return false;
}
return valid;
}
}