Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit 7141986

Browse files
authored
fix: replace tweetnacl impl
* Install `@noble/ed25519` and create a shim * Replace `tweetnacl` with `@noble/ed25519` in `Account` class * Replace `tweetnacl` with `@noble/ed25519` in `Keypair` class * Replace `tweetnacl` with `@noble/ed25519` in `PublicKey` class * Replace `tweetnacl` with `@noble/ed25519` in `Ed25519Program` class * Replace `tweetnacl` with `@noble/ed25519` in `Transaction` class * Replace `tweetnacl` with `@noble/ed25519` in versioned `Transaction` class * Remove `tweetnacl` from project * Damnit, typedoc.
1 parent ef5a6da commit 7141986

15 files changed

+135
-129
lines changed

package-lock.json

+22-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
},
5959
"dependencies": {
6060
"@babel/runtime": "^7.12.5",
61+
"@noble/ed25519": "^1.7.0",
6162
"@noble/hashes": "^1.1.2",
6263
"@noble/secp256k1": "^1.6.3",
6364
"@solana/buffer-layout": "^4.0.0",
@@ -71,8 +72,7 @@
7172
"js-sha3": "^0.8.0",
7273
"node-fetch": "2",
7374
"rpc-websockets": "^7.5.0",
74-
"superstruct": "^0.14.2",
75-
"tweetnacl": "^1.0.3"
75+
"superstruct": "^0.14.2"
7676
},
7777
"devDependencies": {
7878
"@babel/core": "^7.12.13",

rollup.config.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ function generateConfig(configType, format) {
9797
/@babel\/runtime/,
9898
'@noble/hashes/hmac',
9999
'@noble/hashes/sha256',
100+
'@noble/hashes/sha512',
101+
'@noble/ed25519',
100102
'@noble/secp256k1',
101103
'@solana/buffer-layout',
102104
'bigint-buffer',
@@ -110,7 +112,6 @@ function generateConfig(configType, format) {
110112
'node-fetch',
111113
'rpc-websockets',
112114
'superstruct',
113-
'tweetnacl',
114115
];
115116
}
116117

@@ -163,6 +164,8 @@ function generateConfig(configType, format) {
163164
'@solana/buffer-layout',
164165
'@noble/hashes/hmac',
165166
'@noble/hashes/sha256',
167+
'@noble/hashes/sha512',
168+
'@noble/ed25519',
166169
'@noble/secp256k1',
167170
'bigint-buffer',
168171
'bn.js',
@@ -178,7 +181,6 @@ function generateConfig(configType, format) {
178181
'react-native-url-polyfill',
179182
'rpc-websockets',
180183
'superstruct',
181-
'tweetnacl',
182184
];
183185

184186
break;

src/account.ts

+18-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import nacl from 'tweetnacl';
2-
import type {SignKeyPair as KeyPair} from 'tweetnacl';
3-
import type {Buffer} from 'buffer';
1+
import {Buffer} from 'buffer';
42

3+
import {generatePrivateKey, getPublicKey} from './utils/ed25519';
54
import {toBuffer} from './utils/to-buffer';
65
import {PublicKey} from './publickey';
76

@@ -12,7 +11,9 @@ import {PublicKey} from './publickey';
1211
*/
1312
export class Account {
1413
/** @internal */
15-
_keypair: KeyPair;
14+
private _publicKey: Buffer;
15+
/** @internal */
16+
private _secretKey: Buffer;
1617

1718
/**
1819
* Create a new Account object
@@ -24,23 +25,31 @@ export class Account {
2425
*/
2526
constructor(secretKey?: Buffer | Uint8Array | Array<number>) {
2627
if (secretKey) {
27-
this._keypair = nacl.sign.keyPair.fromSecretKey(toBuffer(secretKey));
28+
const secretKeyBuffer = toBuffer(secretKey);
29+
if (secretKey.length !== 64) {
30+
throw new Error('bad secret key size');
31+
}
32+
this._publicKey = secretKeyBuffer.slice(32, 64);
33+
this._secretKey = secretKeyBuffer.slice(0, 32);
2834
} else {
29-
this._keypair = nacl.sign.keyPair();
35+
this._secretKey = toBuffer(generatePrivateKey());
36+
this._publicKey = toBuffer(getPublicKey(this._secretKey));
3037
}
3138
}
3239

3340
/**
3441
* The public key for this account
3542
*/
3643
get publicKey(): PublicKey {
37-
return new PublicKey(this._keypair.publicKey);
44+
return new PublicKey(this._publicKey);
3845
}
3946

4047
/**
41-
* The **unencrypted** secret key for this account
48+
* The **unencrypted** secret key for this account. The first 32 bytes
49+
* is the private scalar and the last 32 bytes is the public key.
50+
* Read more: https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/
4251
*/
4352
get secretKey(): Buffer {
44-
return toBuffer(this._keypair.secretKey);
53+
return Buffer.concat([this._secretKey, this._publicKey], 64);
4554
}
4655
}

src/keypair.ts

+19-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import nacl from 'tweetnacl';
2-
1+
import {generateKeypair, getPublicKey, Ed25519Keypair} from './utils/ed25519';
32
import {PublicKey} from './publickey';
43

54
/**
@@ -10,14 +9,6 @@ export interface Signer {
109
secretKey: Uint8Array;
1110
}
1211

13-
/**
14-
* Ed25519 Keypair
15-
*/
16-
export interface Ed25519Keypair {
17-
publicKey: Uint8Array;
18-
secretKey: Uint8Array;
19-
}
20-
2112
/**
2213
* An account keypair used for signing transactions.
2314
*/
@@ -31,18 +22,14 @@ export class Keypair {
3122
* @param keypair ed25519 keypair
3223
*/
3324
constructor(keypair?: Ed25519Keypair) {
34-
if (keypair) {
35-
this._keypair = keypair;
36-
} else {
37-
this._keypair = nacl.sign.keyPair();
38-
}
25+
this._keypair = keypair ?? generateKeypair();
3926
}
4027

4128
/**
4229
* Generate a new random keypair
4330
*/
4431
static generate(): Keypair {
45-
return new Keypair(nacl.sign.keyPair());
32+
return new Keypair(generateKeypair());
4633
}
4734

4835
/**
@@ -61,16 +48,20 @@ export class Keypair {
6148
secretKey: Uint8Array,
6249
options?: {skipValidation?: boolean},
6350
): Keypair {
64-
const keypair = nacl.sign.keyPair.fromSecretKey(secretKey);
51+
if (secretKey.byteLength !== 64) {
52+
throw new Error('bad secret key size');
53+
}
54+
const publicKey = secretKey.slice(32, 64);
6555
if (!options || !options.skipValidation) {
66-
const encoder = new TextEncoder();
67-
const signData = encoder.encode('@solana/web3.js-validation-v1');
68-
const signature = nacl.sign.detached(signData, keypair.secretKey);
69-
if (!nacl.sign.detached.verify(signData, signature, keypair.publicKey)) {
70-
throw new Error('provided secretKey is invalid');
56+
const privateScalar = secretKey.slice(0, 32);
57+
const computedPublicKey = getPublicKey(privateScalar);
58+
for (let ii = 0; ii < 32; ii++) {
59+
if (publicKey[ii] !== computedPublicKey[ii]) {
60+
throw new Error('provided secretKey is invalid');
61+
}
7162
}
7263
}
73-
return new Keypair(keypair);
64+
return new Keypair({publicKey, secretKey});
7465
}
7566

7667
/**
@@ -79,7 +70,11 @@ export class Keypair {
7970
* @param seed seed byte array
8071
*/
8172
static fromSeed(seed: Uint8Array): Keypair {
82-
return new Keypair(nacl.sign.keyPair.fromSeed(seed));
73+
const publicKey = getPublicKey(seed);
74+
const secretKey = new Uint8Array(64);
75+
secretKey.set(seed);
76+
secretKey.set(publicKey, 32);
77+
return new Keypair({publicKey, secretKey});
8378
}
8479

8580
/**

src/programs/ed25519.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import {Buffer} from 'buffer';
22
import * as BufferLayout from '@solana/buffer-layout';
3-
import nacl from 'tweetnacl';
43

54
import {Keypair} from '../keypair';
65
import {PublicKey} from '../publickey';
76
import {TransactionInstruction} from '../transaction';
87
import assert from '../utils/assert';
8+
import {sign} from '../utils/ed25519';
99

1010
const PRIVATE_KEY_BYTES = 64;
1111
const PUBLIC_KEY_BYTES = 32;
@@ -142,7 +142,7 @@ export class Ed25519Program {
142142
try {
143143
const keypair = Keypair.fromSecretKey(privateKey);
144144
const publicKey = keypair.publicKey.toBytes();
145-
const signature = nacl.sign.detached(message, keypair.secretKey);
145+
const signature = sign(message, keypair.secretKey);
146146

147147
return this.createInstructionWithPublicKey({
148148
publicKey,

src/publickey.ts

+3-66
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import BN from 'bn.js';
22
import bs58 from 'bs58';
33
import {Buffer} from 'buffer';
4-
import nacl from 'tweetnacl';
54
import {sha256} from '@noble/hashes/sha256';
65

6+
import {isOnCurve} from './utils/ed25519';
77
import {Struct, SOLANA_SCHEMA} from './utils/borsh-schema';
88
import {toBuffer} from './utils/to-buffer';
99

@@ -165,7 +165,7 @@ export class PublicKey extends Struct {
165165
Buffer.from('ProgramDerivedAddress'),
166166
]);
167167
const publicKeyBytes = sha256(buffer);
168-
if (is_on_curve(publicKeyBytes)) {
168+
if (isOnCurve(publicKeyBytes)) {
169169
throw new Error(`Invalid seeds, address must fall off the curve`);
170170
}
171171
return new PublicKey(publicKeyBytes);
@@ -228,74 +228,11 @@ export class PublicKey extends Struct {
228228
*/
229229
static isOnCurve(pubkeyData: PublicKeyInitData): boolean {
230230
const pubkey = new PublicKey(pubkeyData);
231-
return is_on_curve(pubkey.toBytes()) == 1;
231+
return isOnCurve(pubkey.toBytes());
232232
}
233233
}
234234

235235
SOLANA_SCHEMA.set(PublicKey, {
236236
kind: 'struct',
237237
fields: [['_bn', 'u256']],
238238
});
239-
240-
// @ts-ignore
241-
let naclLowLevel = nacl.lowlevel;
242-
243-
// Check that a pubkey is on the curve.
244-
// This function and its dependents were sourced from:
245-
// https://github.com/dchest/tweetnacl-js/blob/f1ec050ceae0861f34280e62498b1d3ed9c350c6/nacl.js#L792
246-
function is_on_curve(p: any) {
247-
var r = [
248-
naclLowLevel.gf(),
249-
naclLowLevel.gf(),
250-
naclLowLevel.gf(),
251-
naclLowLevel.gf(),
252-
];
253-
254-
var t = naclLowLevel.gf(),
255-
chk = naclLowLevel.gf(),
256-
num = naclLowLevel.gf(),
257-
den = naclLowLevel.gf(),
258-
den2 = naclLowLevel.gf(),
259-
den4 = naclLowLevel.gf(),
260-
den6 = naclLowLevel.gf();
261-
262-
naclLowLevel.set25519(r[2], gf1);
263-
naclLowLevel.unpack25519(r[1], p);
264-
naclLowLevel.S(num, r[1]);
265-
naclLowLevel.M(den, num, naclLowLevel.D);
266-
naclLowLevel.Z(num, num, r[2]);
267-
naclLowLevel.A(den, r[2], den);
268-
269-
naclLowLevel.S(den2, den);
270-
naclLowLevel.S(den4, den2);
271-
naclLowLevel.M(den6, den4, den2);
272-
naclLowLevel.M(t, den6, num);
273-
naclLowLevel.M(t, t, den);
274-
275-
naclLowLevel.pow2523(t, t);
276-
naclLowLevel.M(t, t, num);
277-
naclLowLevel.M(t, t, den);
278-
naclLowLevel.M(t, t, den);
279-
naclLowLevel.M(r[0], t, den);
280-
281-
naclLowLevel.S(chk, r[0]);
282-
naclLowLevel.M(chk, chk, den);
283-
if (neq25519(chk, num)) naclLowLevel.M(r[0], r[0], I);
284-
285-
naclLowLevel.S(chk, r[0]);
286-
naclLowLevel.M(chk, chk, den);
287-
if (neq25519(chk, num)) return 0;
288-
return 1;
289-
}
290-
let gf1 = naclLowLevel.gf([1]);
291-
let I = naclLowLevel.gf([
292-
0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7,
293-
0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83,
294-
]);
295-
function neq25519(a: any, b: any) {
296-
var c = new Uint8Array(32),
297-
d = new Uint8Array(32);
298-
naclLowLevel.pack25519(c, a);
299-
naclLowLevel.pack25519(d, b);
300-
return naclLowLevel.crypto_verify_32(c, 0, d, 0);
301-
}

0 commit comments

Comments
 (0)