Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[web3.js] Replace tweetnacl impl #27435

Merged
merged 9 commits into from
Aug 28, 2022
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