-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathPostQuantumWallet.test.ts
More file actions
146 lines (123 loc) · 4.63 KB
/
PostQuantumWallet.test.ts
File metadata and controls
146 lines (123 loc) · 4.63 KB
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createHash } from 'node:crypto';
import { TestContract, wotsKeygen, wotsSign, ALICE, signTestMessage } from 'runar-testing';
const __dirname = dirname(fileURLToPath(import.meta.url));
const source = readFileSync(join(__dirname, 'PostQuantumWallet.runar.ts'), 'utf8');
function toHex(bytes: Uint8Array): string {
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
}
function hexToU8(hex: string): Uint8Array {
const buf = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) buf[i / 2] = parseInt(hex.slice(i, i + 2), 16);
return buf;
}
function hash160(data: Uint8Array): Uint8Array {
const sha = createHash('sha256').update(data).digest();
return createHash('ripemd160').update(sha).digest();
}
// Real ECDSA key from ALICE test key
const ecdsaPubKeyHex = ALICE.pubKey;
const ecdsaPubKey = hexToU8(ecdsaPubKeyHex);
const ecdsaPubKeyHash = hash160(ecdsaPubKey);
const ecdsaSigHex = signTestMessage(ALICE.privKey);
const ecdsaSigBytes = hexToU8(ecdsaSigHex);
// WOTS+ keypair
const seed = new Uint8Array(32);
seed[0] = 0x42;
const pubSeed = new Uint8Array(32);
pubSeed[0] = 0x01;
const { sk, pk } = wotsKeygen(seed, pubSeed);
const wotsPubKeyHash = hash160(pk);
describe('PostQuantumWallet (Hybrid ECDSA + WOTS+)', () => {
it('accepts a valid hybrid spend', () => {
const contract = TestContract.fromSource(source, {
ecdsaPubKeyHash: toHex(ecdsaPubKeyHash),
wotsPubKeyHash: toHex(wotsPubKeyHash),
});
// WOTS-sign the real ECDSA signature bytes (ECDSA sig IS the WOTS message)
const wotsSig = wotsSign(ecdsaSigBytes, sk, pubSeed);
const result = contract.call('spend', {
wotsSig: toHex(wotsSig),
wotsPubKey: toHex(pk),
sig: ecdsaSigHex,
pubKey: ecdsaPubKeyHex,
});
expect(result.success).toBe(true);
});
it('rejects a tampered WOTS+ signature', () => {
const contract = TestContract.fromSource(source, {
ecdsaPubKeyHash: toHex(ecdsaPubKeyHash),
wotsPubKeyHash: toHex(wotsPubKeyHash),
});
const wotsSig = wotsSign(ecdsaSigBytes, sk, pubSeed);
// Tamper with WOTS signature
const tampered = new Uint8Array(wotsSig);
tampered[100]! ^= 0xff;
const result = contract.call('spend', {
wotsSig: toHex(tampered),
wotsPubKey: toHex(pk),
sig: ecdsaSigHex,
pubKey: ecdsaPubKeyHex,
});
expect(result.success).toBe(false);
});
it('rejects wrong ECDSA public key hash', () => {
const contract = TestContract.fromSource(source, {
ecdsaPubKeyHash: toHex(ecdsaPubKeyHash),
wotsPubKeyHash: toHex(wotsPubKeyHash),
});
// Different ECDSA pubkey whose hash160 won't match
const wrongEcdsaPubKey = new Uint8Array(33);
wrongEcdsaPubKey[0] = 0x03;
wrongEcdsaPubKey.fill(0xFF, 1);
const wotsSig = wotsSign(ecdsaSigBytes, sk, pubSeed);
const result = contract.call('spend', {
wotsSig: toHex(wotsSig),
wotsPubKey: toHex(pk),
sig: ecdsaSigHex,
pubKey: toHex(wrongEcdsaPubKey),
});
expect(result.success).toBe(false);
});
it('rejects wrong WOTS+ public key hash', () => {
const contract = TestContract.fromSource(source, {
ecdsaPubKeyHash: toHex(ecdsaPubKeyHash),
wotsPubKeyHash: toHex(wotsPubKeyHash),
});
// Different WOTS keypair whose hash160 won't match
const wrongSeed = new Uint8Array(32);
wrongSeed[0] = 0x99;
const wrongPubSeed = new Uint8Array(32);
wrongPubSeed[0] = 0x77;
const wrongKP = wotsKeygen(wrongSeed, wrongPubSeed);
const wrongWotsSig = wotsSign(ecdsaSigBytes, wrongKP.sk, wrongPubSeed);
const result = contract.call('spend', {
wotsSig: toHex(wrongWotsSig),
wotsPubKey: toHex(wrongKP.pk),
sig: ecdsaSigHex,
pubKey: ecdsaPubKeyHex,
});
expect(result.success).toBe(false);
});
it('rejects WOTS+ signed over wrong ECDSA sig', () => {
const contract = TestContract.fromSource(source, {
ecdsaPubKeyHash: toHex(ecdsaPubKeyHash),
wotsPubKeyHash: toHex(wotsPubKeyHash),
});
// Sign a dummy message with WOTS, but provide the real ECDSA sig to the contract
const dummyMsg = new Uint8Array(ecdsaSigBytes.length);
dummyMsg[0] = 0x30;
dummyMsg[1] = 0xFF; // different from real ECDSA sig
const wotsSig = wotsSign(dummyMsg, sk, pubSeed);
const result = contract.call('spend', {
wotsSig: toHex(wotsSig),
wotsPubKey: toHex(pk),
sig: ecdsaSigHex,
pubKey: ecdsaPubKeyHex,
});
expect(result.success).toBe(false);
});
});