Skip to content

Commit

Permalink
fix: replace tweetnacl impl
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
steveluscher authored Aug 28, 2022
1 parent ad6b280 commit 60f3dc5
Show file tree
Hide file tree
Showing 15 changed files with 135 additions and 129 deletions.
26 changes: 22 additions & 4 deletions web3.js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions web3.js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"@noble/ed25519": "^1.7.0",
"@noble/hashes": "^1.1.2",
"@noble/secp256k1": "^1.6.3",
"@solana/buffer-layout": "^4.0.0",
Expand All @@ -71,8 +72,7 @@
"js-sha3": "^0.8.0",
"node-fetch": "2",
"rpc-websockets": "^7.5.0",
"superstruct": "^0.14.2",
"tweetnacl": "^1.0.3"
"superstruct": "^0.14.2"
},
"devDependencies": {
"@babel/core": "^7.12.13",
Expand Down
6 changes: 4 additions & 2 deletions web3.js/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ function generateConfig(configType, format) {
/@babel\/runtime/,
'@noble/hashes/hmac',
'@noble/hashes/sha256',
'@noble/hashes/sha512',
'@noble/ed25519',
'@noble/secp256k1',
'@solana/buffer-layout',
'bigint-buffer',
Expand All @@ -110,7 +112,6 @@ function generateConfig(configType, format) {
'node-fetch',
'rpc-websockets',
'superstruct',
'tweetnacl',
];
}

Expand Down Expand Up @@ -163,6 +164,8 @@ function generateConfig(configType, format) {
'@solana/buffer-layout',
'@noble/hashes/hmac',
'@noble/hashes/sha256',
'@noble/hashes/sha512',
'@noble/ed25519',
'@noble/secp256k1',
'bigint-buffer',
'bn.js',
Expand All @@ -178,7 +181,6 @@ function generateConfig(configType, format) {
'react-native-url-polyfill',
'rpc-websockets',
'superstruct',
'tweetnacl',
];

break;
Expand Down
27 changes: 18 additions & 9 deletions web3.js/src/account.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import nacl from 'tweetnacl';
import type {SignKeyPair as KeyPair} from 'tweetnacl';
import type {Buffer} from 'buffer';
import {Buffer} from 'buffer';

import {generatePrivateKey, getPublicKey} from './utils/ed25519';
import {toBuffer} from './utils/to-buffer';
import {PublicKey} from './publickey';

Expand All @@ -12,7 +11,9 @@ import {PublicKey} from './publickey';
*/
export class Account {
/** @internal */
_keypair: KeyPair;
private _publicKey: Buffer;
/** @internal */
private _secretKey: Buffer;

/**
* Create a new Account object
Expand All @@ -24,23 +25,31 @@ export class Account {
*/
constructor(secretKey?: Buffer | Uint8Array | Array<number>) {
if (secretKey) {
this._keypair = nacl.sign.keyPair.fromSecretKey(toBuffer(secretKey));
const secretKeyBuffer = toBuffer(secretKey);
if (secretKey.length !== 64) {
throw new Error('bad secret key size');
}
this._publicKey = secretKeyBuffer.slice(32, 64);
this._secretKey = secretKeyBuffer.slice(0, 32);
} else {
this._keypair = nacl.sign.keyPair();
this._secretKey = toBuffer(generatePrivateKey());
this._publicKey = toBuffer(getPublicKey(this._secretKey));
}
}

/**
* The public key for this account
*/
get publicKey(): PublicKey {
return new PublicKey(this._keypair.publicKey);
return new PublicKey(this._publicKey);
}

/**
* The **unencrypted** secret key for this account
* The **unencrypted** secret key for this account. The first 32 bytes
* is the private scalar and the last 32 bytes is the public key.
* Read more: https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/
*/
get secretKey(): Buffer {
return toBuffer(this._keypair.secretKey);
return Buffer.concat([this._secretKey, this._publicKey], 64);
}
}
43 changes: 19 additions & 24 deletions web3.js/src/keypair.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import nacl from 'tweetnacl';

import {generateKeypair, getPublicKey, Ed25519Keypair} from './utils/ed25519';
import {PublicKey} from './publickey';

/**
Expand All @@ -10,14 +9,6 @@ export interface Signer {
secretKey: Uint8Array;
}

/**
* Ed25519 Keypair
*/
export interface Ed25519Keypair {
publicKey: Uint8Array;
secretKey: Uint8Array;
}

/**
* An account keypair used for signing transactions.
*/
Expand All @@ -31,18 +22,14 @@ export class Keypair {
* @param keypair ed25519 keypair
*/
constructor(keypair?: Ed25519Keypair) {
if (keypair) {
this._keypair = keypair;
} else {
this._keypair = nacl.sign.keyPair();
}
this._keypair = keypair ?? generateKeypair();
}

/**
* Generate a new random keypair
*/
static generate(): Keypair {
return new Keypair(nacl.sign.keyPair());
return new Keypair(generateKeypair());
}

/**
Expand All @@ -61,16 +48,20 @@ export class Keypair {
secretKey: Uint8Array,
options?: {skipValidation?: boolean},
): Keypair {
const keypair = nacl.sign.keyPair.fromSecretKey(secretKey);
if (secretKey.byteLength !== 64) {
throw new Error('bad secret key size');
}
const publicKey = secretKey.slice(32, 64);
if (!options || !options.skipValidation) {
const encoder = new TextEncoder();
const signData = encoder.encode('@solana/web3.js-validation-v1');
const signature = nacl.sign.detached(signData, keypair.secretKey);
if (!nacl.sign.detached.verify(signData, signature, keypair.publicKey)) {
throw new Error('provided secretKey is invalid');
const privateScalar = secretKey.slice(0, 32);
const computedPublicKey = getPublicKey(privateScalar);
for (let ii = 0; ii < 32; ii++) {
if (publicKey[ii] !== computedPublicKey[ii]) {
throw new Error('provided secretKey is invalid');
}
}
}
return new Keypair(keypair);
return new Keypair({publicKey, secretKey});
}

/**
Expand All @@ -79,7 +70,11 @@ export class Keypair {
* @param seed seed byte array
*/
static fromSeed(seed: Uint8Array): Keypair {
return new Keypair(nacl.sign.keyPair.fromSeed(seed));
const publicKey = getPublicKey(seed);
const secretKey = new Uint8Array(64);
secretKey.set(seed);
secretKey.set(publicKey, 32);
return new Keypair({publicKey, secretKey});
}

/**
Expand Down
4 changes: 2 additions & 2 deletions web3.js/src/programs/ed25519.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {Buffer} from 'buffer';
import * as BufferLayout from '@solana/buffer-layout';
import nacl from 'tweetnacl';

import {Keypair} from '../keypair';
import {PublicKey} from '../publickey';
import {TransactionInstruction} from '../transaction';
import assert from '../utils/assert';
import {sign} from '../utils/ed25519';

const PRIVATE_KEY_BYTES = 64;
const PUBLIC_KEY_BYTES = 32;
Expand Down Expand Up @@ -142,7 +142,7 @@ export class Ed25519Program {
try {
const keypair = Keypair.fromSecretKey(privateKey);
const publicKey = keypair.publicKey.toBytes();
const signature = nacl.sign.detached(message, keypair.secretKey);
const signature = sign(message, keypair.secretKey);

return this.createInstructionWithPublicKey({
publicKey,
Expand Down
69 changes: 3 additions & 66 deletions web3.js/src/publickey.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import BN from 'bn.js';
import bs58 from 'bs58';
import {Buffer} from 'buffer';
import nacl from 'tweetnacl';
import {sha256} from '@noble/hashes/sha256';

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

Expand Down Expand Up @@ -165,7 +165,7 @@ export class PublicKey extends Struct {
Buffer.from('ProgramDerivedAddress'),
]);
const publicKeyBytes = sha256(buffer);
if (is_on_curve(publicKeyBytes)) {
if (isOnCurve(publicKeyBytes)) {
throw new Error(`Invalid seeds, address must fall off the curve`);
}
return new PublicKey(publicKeyBytes);
Expand Down Expand Up @@ -228,74 +228,11 @@ export class PublicKey extends Struct {
*/
static isOnCurve(pubkeyData: PublicKeyInitData): boolean {
const pubkey = new PublicKey(pubkeyData);
return is_on_curve(pubkey.toBytes()) == 1;
return isOnCurve(pubkey.toBytes());
}
}

SOLANA_SCHEMA.set(PublicKey, {
kind: 'struct',
fields: [['_bn', 'u256']],
});

// @ts-ignore
let naclLowLevel = nacl.lowlevel;

// Check that a pubkey is on the curve.
// This function and its dependents were sourced from:
// https://github.com/dchest/tweetnacl-js/blob/f1ec050ceae0861f34280e62498b1d3ed9c350c6/nacl.js#L792
function is_on_curve(p: any) {
var r = [
naclLowLevel.gf(),
naclLowLevel.gf(),
naclLowLevel.gf(),
naclLowLevel.gf(),
];

var t = naclLowLevel.gf(),
chk = naclLowLevel.gf(),
num = naclLowLevel.gf(),
den = naclLowLevel.gf(),
den2 = naclLowLevel.gf(),
den4 = naclLowLevel.gf(),
den6 = naclLowLevel.gf();

naclLowLevel.set25519(r[2], gf1);
naclLowLevel.unpack25519(r[1], p);
naclLowLevel.S(num, r[1]);
naclLowLevel.M(den, num, naclLowLevel.D);
naclLowLevel.Z(num, num, r[2]);
naclLowLevel.A(den, r[2], den);

naclLowLevel.S(den2, den);
naclLowLevel.S(den4, den2);
naclLowLevel.M(den6, den4, den2);
naclLowLevel.M(t, den6, num);
naclLowLevel.M(t, t, den);

naclLowLevel.pow2523(t, t);
naclLowLevel.M(t, t, num);
naclLowLevel.M(t, t, den);
naclLowLevel.M(t, t, den);
naclLowLevel.M(r[0], t, den);

naclLowLevel.S(chk, r[0]);
naclLowLevel.M(chk, chk, den);
if (neq25519(chk, num)) naclLowLevel.M(r[0], r[0], I);

naclLowLevel.S(chk, r[0]);
naclLowLevel.M(chk, chk, den);
if (neq25519(chk, num)) return 0;
return 1;
}
let gf1 = naclLowLevel.gf([1]);
let I = naclLowLevel.gf([
0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7,
0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83,
]);
function neq25519(a: any, b: any) {
var c = new Uint8Array(32),
d = new Uint8Array(32);
naclLowLevel.pack25519(c, a);
naclLowLevel.pack25519(d, b);
return naclLowLevel.crypto_verify_32(c, 0, d, 0);
}
Loading

0 comments on commit 60f3dc5

Please sign in to comment.