Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0592b16a49 | ||
|
|
fbf85ce732 | ||
|
|
cafe51a6e3 | ||
|
|
43b18ea13b | ||
|
|
fd75293334 | ||
|
|
20c6d11917 | ||
|
|
bbe46843fb | ||
|
|
9e5ad8dc85 | ||
|
|
5b305abe85 | ||
|
|
6b0d9611a5 | ||
|
|
b92866d9b8 | ||
|
|
c8fc24fd8f | ||
|
|
4c6ca2326a | ||
|
|
c660712fee | ||
|
|
5983975ada | ||
|
|
1ed861dbad | ||
|
|
211c887a57 |
114
README.md
114
README.md
@@ -2,15 +2,21 @@
|
|||||||
|
|
||||||
Minimal, zero-dependency JS implementation of elliptic curve cryptography.
|
Minimal, zero-dependency JS implementation of elliptic curve cryptography.
|
||||||
|
|
||||||
Implements Short Weierstrass curves with ECDSA signature scheme.
|
- Short Weierstrass curve with ECDSA signatures
|
||||||
|
- Twisted Edwards curve with EdDSA signatures
|
||||||
|
- Montgomery curve for ECDH key agreement
|
||||||
|
|
||||||
To keep the package minimal, no curve definitions are provided out-of-box.
|
To keep the package minimal, no curve definitions are provided out-of-box. Use `micro-curve-definitions` module:
|
||||||
Main reason for that is the fact hashing library is usually required for full functionality. Use separate package that defines popular curves: `micro-curve-definitions` for P192, P224, P256, P384, P521, secp256k1, stark curve, bn254, pasta (pallas/vesta) - it depends on `@noble/hashes`.
|
|
||||||
|
- It provides P192, P224, P256, P384, P521, secp256k1, stark curve, bn254, pasta (pallas/vesta) short weierstrass curves
|
||||||
|
- It also provides ed25519 and ed448 twisted edwards curves
|
||||||
|
- Main reason for separate package is the fact hashing library (like `@noble/hashes`) is required for full functionality
|
||||||
|
- We may reconsider merging packages in future, when a stable version would be ready
|
||||||
|
|
||||||
Future plans:
|
Future plans:
|
||||||
|
|
||||||
- Edwards, Twisted Edwards & Montgomery curves
|
- hash to curve standard
|
||||||
- hash-to-curve standard
|
- point indistinguishability
|
||||||
- pairings
|
- pairings
|
||||||
|
|
||||||
### This library belongs to _noble_ crypto
|
### This library belongs to _noble_ crypto
|
||||||
@@ -40,30 +46,108 @@ npm install @noble/curves
|
|||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// Short Weierstrass curve
|
import { weierstrass } from '@noble/curves/weierstrass'; // Short Weierstrass curve
|
||||||
import shortw from '@noble/curves/shortw';
|
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
import { hmac } from '@noble/hashes/hmac';
|
import { hmac } from '@noble/hashes/hmac';
|
||||||
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
||||||
|
|
||||||
export const secp256k1 = shortw({
|
const secp256k1 = weierstrass({
|
||||||
a: 0n,
|
a: 0n,
|
||||||
b: 7n,
|
b: 7n,
|
||||||
// Field over which we'll do calculations
|
P: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn,
|
||||||
P: 2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n,
|
|
||||||
// Curve order, total count of valid points in the field
|
|
||||||
n: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n,
|
n: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n,
|
||||||
// Base point (x, y) aka generator point
|
|
||||||
Gx: 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
|
Gx: 55066263022277343669578718895168534326250603453777594175500187360389116729240n,
|
||||||
Gy: 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
|
Gy: 32670510020758816978083085130507043184471273380659243275938904335757337482424n,
|
||||||
hash: sha256,
|
hash: sha256,
|
||||||
hmac: (k: Uint8Array, ...msgs: Uint8Array[]) => hmac(sha256, key, concatBytes(...msgs)),
|
hmac: (k: Uint8Array, ...msgs: Uint8Array[]) => hmac(sha256, key, concatBytes(...msgs)),
|
||||||
randomBytes: randomBytes
|
randomBytes
|
||||||
});
|
});
|
||||||
|
|
||||||
// secp256k1.getPublicKey(priv)
|
secp256k1.getPublicKey(secp256k1.utils.randomPrivateKey());
|
||||||
// secp256k1.sign(msg, priv)
|
secp256k1.sign(randomBytes(32), secp256k1.utils.randomPrivateKey());
|
||||||
// secp256k1.verify(sig, msg, pub)
|
// secp256k1.verify(sig, msg, pub)
|
||||||
|
|
||||||
|
import { twistedEdwards } from '@noble/curves/edwards'; // Twisted Edwards curve
|
||||||
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
|
|
||||||
|
const ed25519 = twistedEdwards({
|
||||||
|
a: -1n,
|
||||||
|
d: 37095705934669439343138083508754565189542113879843219016388785533085940283555n,
|
||||||
|
P: 57896044618658097711785492504343953926634992332820282019728792003956564819949n,
|
||||||
|
n: 7237005577332262213973186563042994240857116359379907606001950938285454250989n,
|
||||||
|
h: 8n,
|
||||||
|
Gx: 15112221349535400772501151409588531511454012693041857206046113283949847762202n,
|
||||||
|
Gy: 46316835694926478169428394003475163141307993866256225615783033603165251855960n,
|
||||||
|
hash: sha512,
|
||||||
|
randomBytes,
|
||||||
|
adjustScalarBytes(bytes) { // could be no-op
|
||||||
|
bytes[0] &= 248;
|
||||||
|
bytes[31] &= 127;
|
||||||
|
bytes[31] |= 64;
|
||||||
|
return bytes;
|
||||||
|
},
|
||||||
|
} as const);
|
||||||
|
ed25519.getPublicKey(ed25519.utils.randomPrivateKey());
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
Benchmark results on Apple M2 with node v18.10:
|
||||||
|
|
||||||
|
```
|
||||||
|
==== secp256k1 ====
|
||||||
|
- getPublicKey1 (samples: 10000)
|
||||||
|
noble_old x 8,131 ops/sec @ 122μs/op
|
||||||
|
secp256k1 x 7,374 ops/sec @ 135μs/op
|
||||||
|
- getPublicKey255 (samples: 10000)
|
||||||
|
noble_old x 7,894 ops/sec @ 126μs/op
|
||||||
|
secp256k1 x 7,327 ops/sec @ 136μs/op
|
||||||
|
- sign (samples: 5000)
|
||||||
|
noble_old x 5,243 ops/sec @ 190μs/op
|
||||||
|
secp256k1 x 4,834 ops/sec @ 206μs/op
|
||||||
|
- getSharedSecret (samples: 1000)
|
||||||
|
noble_old x 653 ops/sec @ 1ms/op
|
||||||
|
secp256k1 x 634 ops/sec @ 1ms/op
|
||||||
|
- verify (samples: 1000)
|
||||||
|
secp256k1_old x 1,038 ops/sec @ 962μs/op
|
||||||
|
secp256k1 x 1,009 ops/sec @ 990μs/op
|
||||||
|
==== ed25519 ====
|
||||||
|
- getPublicKey (samples: 10000)
|
||||||
|
old x 8,632 ops/sec @ 115μs/op
|
||||||
|
noble x 8,390 ops/sec @ 119μs/op
|
||||||
|
- sign (samples: 5000)
|
||||||
|
old x 4,376 ops/sec @ 228μs/op
|
||||||
|
noble x 4,233 ops/sec @ 236μs/op
|
||||||
|
- verify (samples: 1000)
|
||||||
|
old x 865 ops/sec @ 1ms/op
|
||||||
|
noble x 860 ops/sec @ 1ms/op
|
||||||
|
==== ed448 ====
|
||||||
|
- getPublicKey (samples: 5000)
|
||||||
|
noble x 3,224 ops/sec @ 310μs/op
|
||||||
|
- sign (samples: 2500)
|
||||||
|
noble x 1,561 ops/sec @ 640μs/op
|
||||||
|
- verify (samples: 500)
|
||||||
|
noble x 313 ops/sec @ 3ms/op
|
||||||
|
==== nist ====
|
||||||
|
- getPublicKey (samples: 2500)
|
||||||
|
P256 x 7,993 ops/sec @ 125μs/op
|
||||||
|
P384 x 3,819 ops/sec @ 261μs/op
|
||||||
|
P521 x 2,074 ops/sec @ 481μs/op
|
||||||
|
- sign (samples: 1000)
|
||||||
|
P256 x 5,327 ops/sec @ 187μs/op
|
||||||
|
P384 x 2,728 ops/sec @ 366μs/op
|
||||||
|
P521 x 1,594 ops/sec @ 626μs/op
|
||||||
|
- verify (samples: 250)
|
||||||
|
P256 x 806 ops/sec @ 1ms/op
|
||||||
|
P384 x 353 ops/sec @ 2ms/op
|
||||||
|
P521 x 171 ops/sec @ 5ms/op
|
||||||
|
==== stark ====
|
||||||
|
- pedersen (samples: 500)
|
||||||
|
old x 85 ops/sec @ 11ms/op
|
||||||
|
noble x 1,216 ops/sec @ 822μs/op
|
||||||
|
- verify (samples: 500)
|
||||||
|
old x 302 ops/sec @ 3ms/op
|
||||||
|
noble x 698 ops/sec @ 1ms/op
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
231
curve-definitions/benchmark/index.js
Normal file
231
curve-definitions/benchmark/index.js
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
import * as bench from 'micro-bmark';
|
||||||
|
const { run, mark } = bench; // or bench.mark
|
||||||
|
// Curves
|
||||||
|
import { secp256k1 } from '../lib/secp256k1.js';
|
||||||
|
import { P256 } from '../lib/p256.js';
|
||||||
|
import { P384 } from '../lib/p384.js';
|
||||||
|
import { P521 } from '../lib/p521.js';
|
||||||
|
import { ed25519 } from '../lib/ed25519.js';
|
||||||
|
import { ed448 } from '../lib/ed448.js';
|
||||||
|
|
||||||
|
// Others
|
||||||
|
import { hmac } from '@noble/hashes/hmac';
|
||||||
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
|
|
||||||
|
import * as old_secp from '@noble/secp256k1';
|
||||||
|
import { concatBytes, hexToBytes } from '@noble/hashes/utils';
|
||||||
|
|
||||||
|
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
|
||||||
|
import * as stark from '../lib/stark.js';
|
||||||
|
|
||||||
|
old_secp.utils.sha256Sync = (...msgs) =>
|
||||||
|
sha256
|
||||||
|
.create()
|
||||||
|
.update(concatBytes(...msgs))
|
||||||
|
.digest();
|
||||||
|
old_secp.utils.hmacSha256Sync = (key, ...msgs) =>
|
||||||
|
hmac
|
||||||
|
.create(sha256, key)
|
||||||
|
.update(concatBytes(...msgs))
|
||||||
|
.digest();
|
||||||
|
import * as noble_ed25519 from '@noble/ed25519';
|
||||||
|
noble_ed25519.utils.sha512Sync = (...m) => sha512(concatBytes(...m));
|
||||||
|
|
||||||
|
for (let item of [secp256k1, ed25519, ed448, P256, P384, P521, old_secp, noble_ed25519]) {
|
||||||
|
item.utils.precompute(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ONLY_NOBLE = process.argv[2] === 'noble';
|
||||||
|
|
||||||
|
function generateData(namespace) {
|
||||||
|
const priv = namespace.utils.randomPrivateKey();
|
||||||
|
const pub = namespace.getPublicKey(priv);
|
||||||
|
const msg = namespace.utils.randomPrivateKey();
|
||||||
|
const sig = namespace.sign(msg, priv);
|
||||||
|
return { priv, pub, msg, sig };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CURVES = {
|
||||||
|
secp256k1: {
|
||||||
|
data: () => {
|
||||||
|
return generateData(secp256k1);
|
||||||
|
},
|
||||||
|
getPublicKey1: {
|
||||||
|
samples: 10000,
|
||||||
|
secp256k1_old: () => old_secp.getPublicKey(3n),
|
||||||
|
secp256k1: () => secp256k1.getPublicKey(3n),
|
||||||
|
},
|
||||||
|
getPublicKey255: {
|
||||||
|
samples: 10000,
|
||||||
|
secp256k1_old: () => old_secp.getPublicKey(2n**255n-1n),
|
||||||
|
secp256k1: () => secp256k1.getPublicKey(2n**255n-1n),
|
||||||
|
},
|
||||||
|
sign: {
|
||||||
|
samples: 5000,
|
||||||
|
secp256k1_old: ({ msg, priv }) => old_secp.signSync(msg, priv),
|
||||||
|
secp256k1: ({ msg, priv }) => secp256k1.sign(msg, priv),
|
||||||
|
},
|
||||||
|
verify: {
|
||||||
|
samples: 1000,
|
||||||
|
secp256k1_old: ({ sig, msg, pub }) => {
|
||||||
|
return old_secp.verify((new old_secp.Signature(sig.r, sig.s)), msg, pub);
|
||||||
|
},
|
||||||
|
secp256k1: ({ sig, msg, pub }) => secp256k1.verify(sig, msg, pub)
|
||||||
|
},
|
||||||
|
getSharedSecret: {
|
||||||
|
samples: 1000,
|
||||||
|
secp256k1_old: ({ pub, priv }) => old_secp.getSharedSecret(priv, pub),
|
||||||
|
secp256k1: ({ pub, priv }) => secp256k1.getSharedSecret(priv, pub),
|
||||||
|
},
|
||||||
|
recoverPublicKey: {
|
||||||
|
samples: 1000,
|
||||||
|
secp256k1_old: ({ sig, msg }) =>
|
||||||
|
old_secp.recoverPublicKey(msg, (new old_secp.Signature(sig.r, sig.s)), sig.recovery),
|
||||||
|
secp256k1: ({ sig, msg }) => sig.recoverPublicKey(msg)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ed25519: {
|
||||||
|
data: () => {
|
||||||
|
function to32Bytes(numOrStr) {
|
||||||
|
const hex = typeof numOrStr === 'string' ? numOrStr : numOrStr.toString(16);
|
||||||
|
return hexToBytes(hex.padStart(64, '0'));
|
||||||
|
}
|
||||||
|
const priv = to32Bytes(0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60n);
|
||||||
|
const pub = noble_ed25519.sync.getPublicKey(priv);
|
||||||
|
const msg = to32Bytes('deadbeefdeadbeefdeadbeefdeadbeefdeadbeef');
|
||||||
|
const sig = noble_ed25519.sync.sign(msg, priv);
|
||||||
|
return { pub, priv, msg, sig };
|
||||||
|
},
|
||||||
|
getPublicKey: {
|
||||||
|
samples: 10000,
|
||||||
|
old: () => noble_ed25519.sync.getPublicKey(noble_ed25519.utils.randomPrivateKey()),
|
||||||
|
noble: () => ed25519.getPublicKey(ed25519.utils.randomPrivateKey()),
|
||||||
|
},
|
||||||
|
sign: {
|
||||||
|
samples: 5000,
|
||||||
|
old: ({ msg, priv }) => noble_ed25519.sync.sign(msg, priv),
|
||||||
|
noble: ({ msg, priv }) => ed25519.sign(msg, priv),
|
||||||
|
},
|
||||||
|
verify: {
|
||||||
|
samples: 1000,
|
||||||
|
old: ({ sig, msg, pub }) => noble_ed25519.sync.verify(sig, msg, pub),
|
||||||
|
noble: ({ sig, msg, pub }) => ed25519.verify(sig, msg, pub),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ed448: {
|
||||||
|
data: () => {
|
||||||
|
const priv = ed448.utils.randomPrivateKey();
|
||||||
|
const pub = ed448.getPublicKey(priv);
|
||||||
|
const msg = ed448.utils.randomPrivateKey();
|
||||||
|
const sig = ed448.sign(msg, priv);
|
||||||
|
return { priv, pub, msg, sig };
|
||||||
|
},
|
||||||
|
getPublicKey: {
|
||||||
|
samples: 5000,
|
||||||
|
noble: () => ed448.getPublicKey(ed448.utils.randomPrivateKey()),
|
||||||
|
},
|
||||||
|
sign: {
|
||||||
|
samples: 2500,
|
||||||
|
noble: ({ msg, priv }) => ed448.sign(msg, priv),
|
||||||
|
},
|
||||||
|
verify: {
|
||||||
|
samples: 500,
|
||||||
|
noble: ({ sig, msg, pub }) => ed448.verify(sig, msg, pub)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nist: {
|
||||||
|
data: () => {
|
||||||
|
return { p256: generateData(P256), p384: generateData(P384), p521: generateData(P521) }
|
||||||
|
},
|
||||||
|
getPublicKey: {
|
||||||
|
samples: 2500,
|
||||||
|
P256: () => P256.getPublicKey(P256.utils.randomPrivateKey()),
|
||||||
|
P384: () => P384.getPublicKey(P384.utils.randomPrivateKey()),
|
||||||
|
P521: () => P521.getPublicKey(P521.utils.randomPrivateKey()),
|
||||||
|
},
|
||||||
|
sign: {
|
||||||
|
samples: 1000,
|
||||||
|
P256: ({ p256: {msg, priv} }) => P256.sign(msg, priv),
|
||||||
|
P384: ({ p384: {msg, priv} }) => P384.sign(msg, priv),
|
||||||
|
P521: ({ p521: {msg, priv} }) => P521.sign(msg, priv),
|
||||||
|
},
|
||||||
|
verify: {
|
||||||
|
samples: 250,
|
||||||
|
P256: ({ p256: {sig, msg, pub} }) => P256.verify(sig, msg, pub),
|
||||||
|
P384: ({ p384: {sig, msg, pub} }) => P384.verify(sig, msg, pub),
|
||||||
|
P521: ({ p521: {sig, msg, pub} }) => P521.verify(sig, msg, pub),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stark: {
|
||||||
|
data: () => {
|
||||||
|
const priv = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
|
||||||
|
const msg = 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47';
|
||||||
|
const pub = stark.getPublicKey(priv);
|
||||||
|
const sig = stark.sign(msg, priv);
|
||||||
|
|
||||||
|
const privateKey = '2dccce1da22003777062ee0870e9881b460a8b7eca276870f57c601f182136c';
|
||||||
|
const msgHash = 'c465dd6b1bbffdb05442eb17f5ca38ad1aa78a6f56bf4415bdee219114a47';
|
||||||
|
const keyPair = starkwareCrypto.default.ec.keyFromPrivate(privateKey, 'hex');
|
||||||
|
const publicKeyStark = starkwareCrypto.default.ec.keyFromPublic(
|
||||||
|
keyPair.getPublic(true, 'hex'), 'hex'
|
||||||
|
);
|
||||||
|
|
||||||
|
return { priv, sig, msg, pub, publicKeyStark, msgHash, keyPair }
|
||||||
|
},
|
||||||
|
pedersen: {
|
||||||
|
samples: 500,
|
||||||
|
old: () => {
|
||||||
|
return starkwareCrypto.default.pedersen([
|
||||||
|
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
|
||||||
|
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a',
|
||||||
|
])
|
||||||
|
},
|
||||||
|
noble: () => {
|
||||||
|
return stark.pedersen(
|
||||||
|
'3d937c035c878245caf64531a5756109c53068da139362728feb561405371cb',
|
||||||
|
'208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
verify: {
|
||||||
|
samples: 500,
|
||||||
|
old: ({ publicKeyStark, msgHash, keyPair }) => {
|
||||||
|
return starkwareCrypto.default.verify(
|
||||||
|
publicKeyStark,
|
||||||
|
msgHash,
|
||||||
|
starkwareCrypto.default.sign(keyPair, msgHash)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
noble: ({ priv, msg, pub }) => {
|
||||||
|
return stark.verify(stark.sign(msg, priv), msg, pub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const main = () =>
|
||||||
|
run(async () => {
|
||||||
|
for (const [name, curve] of Object.entries(CURVES)) {
|
||||||
|
console.log(`==== ${name} ====`);
|
||||||
|
const data = curve.data();
|
||||||
|
for (const [fnName, libs] of Object.entries(curve)) {
|
||||||
|
if (fnName === 'data') continue;
|
||||||
|
const samples = libs.samples;
|
||||||
|
console.log(` - ${fnName} (samples: ${samples})`);
|
||||||
|
for (const [lib, fn] of Object.entries(libs)) {
|
||||||
|
if (lib === 'samples') continue;
|
||||||
|
if (ONLY_NOBLE && lib !== 'noble') continue;
|
||||||
|
await mark(` ${lib}`, samples, () => fn(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Log current RAM
|
||||||
|
bench.logMem();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESM is broken.
|
||||||
|
import url from 'url';
|
||||||
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
main();
|
||||||
|
}
|
||||||
22
curve-definitions/benchmark/package.json
Normal file
22
curve-definitions/benchmark/package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "benchmark",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "benchmarks",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"bench": "node index.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"micro-bmark": "0.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/ed25519": "^1.7.1",
|
||||||
|
"@noble/secp256k1": "^1.7.0",
|
||||||
|
"@starkware-industries/starkware-crypto-utils": "^0.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,26 +1,23 @@
|
|||||||
{
|
{
|
||||||
"name": "micro-curve-definitions",
|
"name": "micro-curve-definitions",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"description": "Curve definitions for @noble/curves",
|
"description": "Curve definitions for @noble/curves",
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"lib"
|
||||||
"index.d.ts",
|
|
||||||
"index.d.ts.map",
|
|
||||||
"index.ts"
|
|
||||||
],
|
],
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "index.js",
|
"main": "lib/index.js",
|
||||||
"module": "index.js",
|
"module": "lib/index.js",
|
||||||
"types": "index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/curves": "~0.1.0",
|
"@noble/curves": "0.2.0",
|
||||||
"@noble/hashes": "1.1.4"
|
"@noble/hashes": "1.1.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scure/base": "~1.1.0",
|
"@scure/base": "~1.1.0",
|
||||||
"@scure/bip32": "^1.1.1",
|
"@scure/bip32": "^1.1.1",
|
||||||
"@scure/bip39": "^1.1.0",
|
"@scure/bip39": "^1.1.0",
|
||||||
"@types/node": "^18.11.3",
|
"@types/node": "18.11.3",
|
||||||
"fast-check": "3.0.0",
|
"fast-check": "3.0.0",
|
||||||
"micro-should": "0.2.0",
|
"micro-should": "0.2.0",
|
||||||
"prettier": "2.6.2",
|
"prettier": "2.6.2",
|
||||||
@@ -35,7 +32,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"lint": "prettier --check index.ts",
|
"lint": "prettier --check src",
|
||||||
"test": "node test/index.test.js"
|
"test": "node test/index.test.js"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -59,4 +56,4 @@
|
|||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
17
curve-definitions/src/_shortw_utils.ts
Normal file
17
curve-definitions/src/_shortw_utils.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { hmac } from '@noble/hashes/hmac';
|
||||||
|
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
||||||
|
import { weierstrass, CurveType, CHash } from '@noble/curves/weierstrass';
|
||||||
|
|
||||||
|
export function getHash(hash: CHash) {
|
||||||
|
return {
|
||||||
|
hash,
|
||||||
|
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(hash, key, concatBytes(...msgs)),
|
||||||
|
randomBytes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Same API as @noble/hashes, with ability to create curve with custom hash
|
||||||
|
type CurveDef = Readonly<Omit<CurveType, 'hash' | 'hmac' | 'randomBytes'>>;
|
||||||
|
export function createCurve(curveDef: CurveDef, defHash: CHash) {
|
||||||
|
const create = (hash: CHash) => weierstrass({ ...curveDef, ...getHash(hash) });
|
||||||
|
return Object.freeze({ ...create(defHash), create });
|
||||||
|
}
|
||||||
@@ -1,25 +1,21 @@
|
|||||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
import { weierstrass, CHash } from '@noble/curves/shortw';
|
import { weierstrass } from '@noble/curves/weierstrass';
|
||||||
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
|
||||||
import { hmac } from '@noble/hashes/hmac';
|
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
|
import { getHash } from './_shortw_utils.js';
|
||||||
|
|
||||||
function getHash(hash: CHash) {
|
/**
|
||||||
return {
|
* bn254 pairing-friendly curve.
|
||||||
hash,
|
* Previously known as alt_bn_128, when it had 128-bit security.
|
||||||
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(hash, key, concatBytes(...msgs)),
|
* Recent research shown it's weaker, the naming has been adjusted to its prime bit count.
|
||||||
randomBytes,
|
* https://github.com/zcash/zcash/issues/2502
|
||||||
};
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
// Was known as alt_bn128 when it had 128-bit security. Now that it's much lower, the naming
|
|
||||||
// has been changed to its prime bit count.
|
|
||||||
export const bn254 = weierstrass({
|
export const bn254 = weierstrass({
|
||||||
a: 0n,
|
a: BigInt(0),
|
||||||
b: 3n,
|
b: BigInt(3),
|
||||||
P: 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47n,
|
P: BigInt('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47'),
|
||||||
n: 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n,
|
n: BigInt('0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001'),
|
||||||
Gx: 1n,
|
Gx: BigInt(1),
|
||||||
Gy: 2n,
|
Gy: BigInt(2),
|
||||||
|
h: BigInt(1),
|
||||||
...getHash(sha256),
|
...getHash(sha256),
|
||||||
});
|
});
|
||||||
|
|||||||
341
curve-definitions/src/ed25519.ts
Normal file
341
curve-definitions/src/ed25519.ts
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
|
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
|
||||||
|
import { twistedEdwards, ExtendedPointType } from '@noble/curves/edwards';
|
||||||
|
import { montgomery } from '@noble/curves/montgomery';
|
||||||
|
import { mod, pow2, isNegativeLE } from '@noble/curves/modular';
|
||||||
|
import {
|
||||||
|
ensureBytes,
|
||||||
|
equalBytes,
|
||||||
|
bytesToHex,
|
||||||
|
bytesToNumberLE,
|
||||||
|
numberToBytesLE,
|
||||||
|
Hex,
|
||||||
|
} from '@noble/curves/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ed25519 Twisted Edwards curve with following addons:
|
||||||
|
* - X25519 ECDH
|
||||||
|
* - Ristretto cofactor elimination
|
||||||
|
* - Elligator hash-to-group / point indistinguishability
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ED25519_P = BigInt(
|
||||||
|
'57896044618658097711785492504343953926634992332820282019728792003956564819949'
|
||||||
|
);
|
||||||
|
// √(-1) aka √(a) aka 2^((p-1)/4)
|
||||||
|
const ED25519_SQRT_M1 = BigInt(
|
||||||
|
'19681161376707505956807079304988542015446066515923890162744021073123829784752'
|
||||||
|
);
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _5n = BigInt(5);
|
||||||
|
// prettier-ignore
|
||||||
|
const _10n = BigInt(10), _20n = BigInt(20), _40n = BigInt(40), _80n = BigInt(80);
|
||||||
|
function ed25519_pow_2_252_3(x: bigint) {
|
||||||
|
const P = ED25519_P;
|
||||||
|
const x2 = (x * x) % P;
|
||||||
|
const b2 = (x2 * x) % P; // x^3, 11
|
||||||
|
const b4 = (pow2(b2, _2n, P) * b2) % P; // x^15, 1111
|
||||||
|
const b5 = (pow2(b4, _1n, P) * x) % P; // x^31
|
||||||
|
const b10 = (pow2(b5, _5n, P) * b5) % P;
|
||||||
|
const b20 = (pow2(b10, _10n, P) * b10) % P;
|
||||||
|
const b40 = (pow2(b20, _20n, P) * b20) % P;
|
||||||
|
const b80 = (pow2(b40, _40n, P) * b40) % P;
|
||||||
|
const b160 = (pow2(b80, _80n, P) * b80) % P;
|
||||||
|
const b240 = (pow2(b160, _80n, P) * b80) % P;
|
||||||
|
const b250 = (pow2(b240, _10n, P) * b10) % P;
|
||||||
|
const pow_p_5_8 = (pow2(b250, _2n, P) * x) % P;
|
||||||
|
// ^ To pow to (p+3)/8, multiply it by x.
|
||||||
|
return { pow_p_5_8, b2 };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For X25519, in order to decode 32 random bytes as an integer scalar,
|
||||||
|
* set the
|
||||||
|
* three least significant bits of the first byte 0b1111_1000,
|
||||||
|
* and the most significant bit of the last to zero 0b0111_1111,
|
||||||
|
* set the second most significant bit of the last byte to 1 0b0100_0000
|
||||||
|
*/
|
||||||
|
function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
|
||||||
|
bytes[0] &= 248;
|
||||||
|
bytes[31] &= 127;
|
||||||
|
bytes[31] |= 64;
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
// sqrt(u/v)
|
||||||
|
function uvRatio(u: bigint, v: bigint): { isValid: boolean; value: bigint } {
|
||||||
|
const P = ED25519_P;
|
||||||
|
const v3 = mod(v * v * v, P); // v³
|
||||||
|
const v7 = mod(v3 * v3 * v, P); // v⁷
|
||||||
|
// (p+3)/8 and (p-5)/8
|
||||||
|
const pow = ed25519_pow_2_252_3(u * v7).pow_p_5_8;
|
||||||
|
let x = mod(u * v3 * pow, P); // (uv³)(uv⁷)^(p-5)/8
|
||||||
|
const vx2 = mod(v * x * x, P); // vx²
|
||||||
|
const root1 = x; // First root candidate
|
||||||
|
const root2 = mod(x * ED25519_SQRT_M1, P); // Second root candidate
|
||||||
|
const useRoot1 = vx2 === u; // If vx² = u (mod p), x is a square root
|
||||||
|
const useRoot2 = vx2 === mod(-u, P); // If vx² = -u, set x <-- x * 2^((p-1)/4)
|
||||||
|
const noRoot = vx2 === mod(-u * ED25519_SQRT_M1, P); // There is no valid root, vx² = -u√(-1)
|
||||||
|
if (useRoot1) x = root1;
|
||||||
|
if (useRoot2 || noRoot) x = root2; // We return root2 anyway, for const-time
|
||||||
|
if (isNegativeLE(x, P)) x = mod(-x, P);
|
||||||
|
return { isValid: useRoot1 || useRoot2, value: x };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just in case
|
||||||
|
export const ED25519_TORSION_SUBGROUP = [
|
||||||
|
'0100000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a',
|
||||||
|
'0000000000000000000000000000000000000000000000000000000000000080',
|
||||||
|
'26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05',
|
||||||
|
'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
|
'26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85',
|
||||||
|
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa',
|
||||||
|
];
|
||||||
|
|
||||||
|
const ED25519_DEF = {
|
||||||
|
// Param: a
|
||||||
|
a: BigInt(-1),
|
||||||
|
// Equal to -121665/121666 over finite field.
|
||||||
|
// Negative number is P - number, and division is invert(number, P)
|
||||||
|
d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'),
|
||||||
|
// Finite field 𝔽p over which we'll do calculations; 2n ** 255n - 19n
|
||||||
|
P: ED25519_P,
|
||||||
|
// Subgroup order: how many points ed25519 has
|
||||||
|
// 2n ** 252n + 27742317777372353535851937790883648493n;
|
||||||
|
n: BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'),
|
||||||
|
// Cofactor
|
||||||
|
h: BigInt(8),
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt('15112221349535400772501151409588531511454012693041857206046113283949847762202'),
|
||||||
|
Gy: BigInt('46316835694926478169428394003475163141307993866256225615783033603165251855960'),
|
||||||
|
hash: sha512,
|
||||||
|
randomBytes,
|
||||||
|
adjustScalarBytes,
|
||||||
|
// dom2
|
||||||
|
// Ratio of u to v. Allows us to combine inversion and square root. Uses algo from RFC8032 5.1.3.
|
||||||
|
// Constant-time, u/√v
|
||||||
|
uvRatio,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const ed25519 = twistedEdwards(ED25519_DEF);
|
||||||
|
function ed25519_domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
|
||||||
|
if (ctx.length > 255) throw new Error('Context is too big');
|
||||||
|
return concatBytes(
|
||||||
|
utf8ToBytes('SigEd25519 no Ed25519 collisions'),
|
||||||
|
new Uint8Array([phflag ? 1 : 0, ctx.length]),
|
||||||
|
ctx,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export const ed25519ctx = twistedEdwards({ ...ED25519_DEF, domain: ed25519_domain });
|
||||||
|
export const ed25519ph = twistedEdwards({
|
||||||
|
...ED25519_DEF,
|
||||||
|
domain: ed25519_domain,
|
||||||
|
preHash: sha512,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const x25519 = montgomery({
|
||||||
|
P: ED25519_P,
|
||||||
|
a24: BigInt('121665'),
|
||||||
|
montgomeryBits: 255, // n is 253 bits
|
||||||
|
nByteLength: 32,
|
||||||
|
Gu: '0900000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
powPminus2: (x: bigint): bigint => {
|
||||||
|
const P = ED25519_P;
|
||||||
|
// x^(p-2) aka x^(2^255-21)
|
||||||
|
const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x);
|
||||||
|
return mod(pow2(pow_p_5_8, BigInt(3), P) * b2, P);
|
||||||
|
},
|
||||||
|
adjustScalarBytes,
|
||||||
|
});
|
||||||
|
|
||||||
|
function assertRstPoint(other: unknown) {
|
||||||
|
if (!(other instanceof RistrettoPoint)) throw new TypeError('RistrettoPoint expected');
|
||||||
|
}
|
||||||
|
// √(-1) aka √(a) aka 2^((p-1)/4)
|
||||||
|
const SQRT_M1 = BigInt(
|
||||||
|
'19681161376707505956807079304988542015446066515923890162744021073123829784752'
|
||||||
|
);
|
||||||
|
// √(ad - 1)
|
||||||
|
const SQRT_AD_MINUS_ONE = BigInt(
|
||||||
|
'25063068953384623474111414158702152701244531502492656460079210482610430750235'
|
||||||
|
);
|
||||||
|
// 1 / √(a-d)
|
||||||
|
const INVSQRT_A_MINUS_D = BigInt(
|
||||||
|
'54469307008909316920995813868745141605393597292927456921205312896311721017578'
|
||||||
|
);
|
||||||
|
// 1-d²
|
||||||
|
const ONE_MINUS_D_SQ = BigInt(
|
||||||
|
'1159843021668779879193775521855586647937357759715417654439879720876111806838'
|
||||||
|
);
|
||||||
|
// (d-1)²
|
||||||
|
const D_MINUS_ONE_SQ = BigInt(
|
||||||
|
'40440834346308536858101042469323190826248399146238708352240133220865137265952'
|
||||||
|
);
|
||||||
|
// Calculates 1/√(number)
|
||||||
|
const invertSqrt = (number: bigint) => uvRatio(_1n, number);
|
||||||
|
|
||||||
|
const MAX_255B = BigInt('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
|
||||||
|
const bytes255ToNumberLE = (bytes: Uint8Array) =>
|
||||||
|
ed25519.utils.mod(bytesToNumberLE(bytes) & MAX_255B);
|
||||||
|
|
||||||
|
type ExtendedPoint = ExtendedPointType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each ed25519/ExtendedPoint has 8 different equivalent points. This can be
|
||||||
|
* a source of bugs for protocols like ring signatures. Ristretto was created to solve this.
|
||||||
|
* Ristretto point operates in X:Y:Z:T extended coordinates like ExtendedPoint,
|
||||||
|
* but it should work in its own namespace: do not combine those two.
|
||||||
|
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-ristretto255-decaf448
|
||||||
|
*/
|
||||||
|
export class RistrettoPoint {
|
||||||
|
static BASE = new RistrettoPoint(ed25519.ExtendedPoint.BASE);
|
||||||
|
static ZERO = new RistrettoPoint(ed25519.ExtendedPoint.ZERO);
|
||||||
|
|
||||||
|
// Private property to discourage combining ExtendedPoint + RistrettoPoint
|
||||||
|
// Always use Ristretto encoding/decoding instead.
|
||||||
|
constructor(private readonly ep: ExtendedPoint) {}
|
||||||
|
|
||||||
|
// Computes Elligator map for Ristretto
|
||||||
|
// https://ristretto.group/formulas/elligator.html
|
||||||
|
private static calcElligatorRistrettoMap(r0: bigint): ExtendedPoint {
|
||||||
|
const { d, P } = ed25519.CURVE;
|
||||||
|
const { mod } = ed25519.utils;
|
||||||
|
const r = mod(SQRT_M1 * r0 * r0); // 1
|
||||||
|
const Ns = mod((r + _1n) * ONE_MINUS_D_SQ); // 2
|
||||||
|
let c = BigInt(-1); // 3
|
||||||
|
const D = mod((c - d * r) * mod(r + d)); // 4
|
||||||
|
let { isValid: Ns_D_is_sq, value: s } = uvRatio(Ns, D); // 5
|
||||||
|
let s_ = mod(s * r0); // 6
|
||||||
|
if (!isNegativeLE(s_, P)) s_ = mod(-s_);
|
||||||
|
if (!Ns_D_is_sq) s = s_; // 7
|
||||||
|
if (!Ns_D_is_sq) c = r; // 8
|
||||||
|
const Nt = mod(c * (r - _1n) * D_MINUS_ONE_SQ - D); // 9
|
||||||
|
const s2 = s * s;
|
||||||
|
const W0 = mod((s + s) * D); // 10
|
||||||
|
const W1 = mod(Nt * SQRT_AD_MINUS_ONE); // 11
|
||||||
|
const W2 = mod(_1n - s2); // 12
|
||||||
|
const W3 = mod(_1n + s2); // 13
|
||||||
|
return new ed25519.ExtendedPoint(mod(W0 * W3), mod(W2 * W1), mod(W1 * W3), mod(W0 * W2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes uniform output of 64-bit hash function like sha512 and converts it to `RistrettoPoint`.
|
||||||
|
* The hash-to-group operation applies Elligator twice and adds the results.
|
||||||
|
* **Note:** this is one-way map, there is no conversion from point to hash.
|
||||||
|
* https://ristretto.group/formulas/elligator.html
|
||||||
|
* @param hex 64-bit output of a hash function
|
||||||
|
*/
|
||||||
|
static hashToCurve(hex: Hex): RistrettoPoint {
|
||||||
|
hex = ensureBytes(hex, 64);
|
||||||
|
const r1 = bytes255ToNumberLE(hex.slice(0, 32));
|
||||||
|
const R1 = this.calcElligatorRistrettoMap(r1);
|
||||||
|
const r2 = bytes255ToNumberLE(hex.slice(32, 64));
|
||||||
|
const R2 = this.calcElligatorRistrettoMap(r2);
|
||||||
|
return new RistrettoPoint(R1.add(R2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts ristretto-encoded string to ristretto point.
|
||||||
|
* https://ristretto.group/formulas/decoding.html
|
||||||
|
* @param hex Ristretto-encoded 32 bytes. Not every 32-byte string is valid ristretto encoding
|
||||||
|
*/
|
||||||
|
static fromHex(hex: Hex): RistrettoPoint {
|
||||||
|
hex = ensureBytes(hex, 32);
|
||||||
|
const { a, d, P } = ed25519.CURVE;
|
||||||
|
const { mod } = ed25519.utils;
|
||||||
|
const emsg = 'RistrettoPoint.fromHex: the hex is not valid encoding of RistrettoPoint';
|
||||||
|
const s = bytes255ToNumberLE(hex);
|
||||||
|
// 1. Check that s_bytes is the canonical encoding of a field element, or else abort.
|
||||||
|
// 3. Check that s is non-negative, or else abort
|
||||||
|
if (!equalBytes(numberToBytesLE(s, 32), hex) || isNegativeLE(s, P)) throw new Error(emsg);
|
||||||
|
const s2 = mod(s * s);
|
||||||
|
const u1 = mod(_1n + a * s2); // 4 (a is -1)
|
||||||
|
const u2 = mod(_1n - a * s2); // 5
|
||||||
|
const u1_2 = mod(u1 * u1);
|
||||||
|
const u2_2 = mod(u2 * u2);
|
||||||
|
const v = mod(a * d * u1_2 - u2_2); // 6
|
||||||
|
const { isValid, value: I } = invertSqrt(mod(v * u2_2)); // 7
|
||||||
|
const Dx = mod(I * u2); // 8
|
||||||
|
const Dy = mod(I * Dx * v); // 9
|
||||||
|
let x = mod((s + s) * Dx); // 10
|
||||||
|
if (isNegativeLE(x, P)) x = mod(-x); // 10
|
||||||
|
const y = mod(u1 * Dy); // 11
|
||||||
|
const t = mod(x * y); // 12
|
||||||
|
if (!isValid || isNegativeLE(t, P) || y === _0n) throw new Error(emsg);
|
||||||
|
return new RistrettoPoint(new ed25519.ExtendedPoint(x, y, _1n, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes ristretto point to Uint8Array.
|
||||||
|
* https://ristretto.group/formulas/encoding.html
|
||||||
|
*/
|
||||||
|
toRawBytes(): Uint8Array {
|
||||||
|
let { x, y, z, t } = this.ep;
|
||||||
|
const { P } = ed25519.CURVE;
|
||||||
|
const { mod } = ed25519.utils;
|
||||||
|
const u1 = mod(mod(z + y) * mod(z - y)); // 1
|
||||||
|
const u2 = mod(x * y); // 2
|
||||||
|
// Square root always exists
|
||||||
|
const u2sq = mod(u2 * u2);
|
||||||
|
const { value: invsqrt } = invertSqrt(mod(u1 * u2sq)); // 3
|
||||||
|
const D1 = mod(invsqrt * u1); // 4
|
||||||
|
const D2 = mod(invsqrt * u2); // 5
|
||||||
|
const zInv = mod(D1 * D2 * t); // 6
|
||||||
|
let D: bigint; // 7
|
||||||
|
if (isNegativeLE(t * zInv, P)) {
|
||||||
|
let _x = mod(y * SQRT_M1);
|
||||||
|
let _y = mod(x * SQRT_M1);
|
||||||
|
x = _x;
|
||||||
|
y = _y;
|
||||||
|
D = mod(D1 * INVSQRT_A_MINUS_D);
|
||||||
|
} else {
|
||||||
|
D = D2; // 8
|
||||||
|
}
|
||||||
|
if (isNegativeLE(x * zInv, P)) y = mod(-y); // 9
|
||||||
|
let s = mod((z - y) * D); // 10 (check footer's note, no sqrt(-a))
|
||||||
|
if (isNegativeLE(s, P)) s = mod(-s);
|
||||||
|
return numberToBytesLE(s, 32); // 11
|
||||||
|
}
|
||||||
|
|
||||||
|
toHex(): string {
|
||||||
|
return bytesToHex(this.toRawBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return this.toHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare one point to another.
|
||||||
|
equals(other: RistrettoPoint): boolean {
|
||||||
|
assertRstPoint(other);
|
||||||
|
const a = this.ep;
|
||||||
|
const b = other.ep;
|
||||||
|
const { mod } = ed25519.utils;
|
||||||
|
// (x1 * y2 == y1 * x2) | (y1 * y2 == x1 * x2)
|
||||||
|
const one = mod(a.x * b.y) === mod(a.y * b.x);
|
||||||
|
const two = mod(a.y * b.y) === mod(a.x * b.x);
|
||||||
|
return one || two;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(other: RistrettoPoint): RistrettoPoint {
|
||||||
|
assertRstPoint(other);
|
||||||
|
return new RistrettoPoint(this.ep.add(other.ep));
|
||||||
|
}
|
||||||
|
|
||||||
|
subtract(other: RistrettoPoint): RistrettoPoint {
|
||||||
|
assertRstPoint(other);
|
||||||
|
return new RistrettoPoint(this.ep.subtract(other.ep));
|
||||||
|
}
|
||||||
|
|
||||||
|
multiply(scalar: number | bigint): RistrettoPoint {
|
||||||
|
return new RistrettoPoint(this.ep.multiply(scalar));
|
||||||
|
}
|
||||||
|
|
||||||
|
multiplyUnsafe(scalar: number | bigint): RistrettoPoint {
|
||||||
|
return new RistrettoPoint(this.ep.multiplyUnsafe(scalar));
|
||||||
|
}
|
||||||
|
}
|
||||||
146
curve-definitions/src/ed448.ts
Normal file
146
curve-definitions/src/ed448.ts
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
import { shake256 } from '@noble/hashes/sha3';
|
||||||
|
import { concatBytes, randomBytes, utf8ToBytes, wrapConstructor } from '@noble/hashes/utils';
|
||||||
|
import { twistedEdwards } from '@noble/curves/edwards';
|
||||||
|
import { mod, pow2 } from '@noble/curves/modular';
|
||||||
|
import { montgomery } from '../../lib/montgomery.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edwards448 (not Ed448-Goldilocks) curve with following addons:
|
||||||
|
* * X448 ECDH
|
||||||
|
* Conforms to RFC 8032 https://www.rfc-editor.org/rfc/rfc8032.html#section-5.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
const shake256_114 = wrapConstructor(() => shake256.create({ dkLen: 114 }));
|
||||||
|
const shake256_64 = wrapConstructor(() => shake256.create({ dkLen: 64 }));
|
||||||
|
const ed448P = BigInt(
|
||||||
|
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018365439'
|
||||||
|
);
|
||||||
|
|
||||||
|
// powPminus3div4 calculates z = x^k mod p, where k = (p-3)/4.
|
||||||
|
function ed448_pow_Pminus3div4(x: bigint): bigint {
|
||||||
|
const P = ed448P;
|
||||||
|
// prettier-ignore
|
||||||
|
let [_1n, _2n, _3n, _11n, _22n, _44n, _88n, _223n] = [1, 2, 3, 11, 22, 44, 88, 223]
|
||||||
|
.map(n => BigInt(n));
|
||||||
|
// x ** ((P - 3n)/4n) % P
|
||||||
|
// [223 of 1, 0, 222 of 1], almost same as secp!
|
||||||
|
const b2 = (x * x * x) % P;
|
||||||
|
const b3 = (b2 * b2 * x) % P;
|
||||||
|
const b6 = (pow2(b3, _3n, P) * b3) % P;
|
||||||
|
const b9 = (pow2(b6, _3n, P) * b3) % P;
|
||||||
|
const b11 = (pow2(b9, _2n, P) * b2) % P;
|
||||||
|
const b22 = (pow2(b11, _11n, P) * b11) % P;
|
||||||
|
const b44 = (pow2(b22, _22n, P) * b22) % P;
|
||||||
|
const b88 = (pow2(b44, _44n, P) * b44) % P;
|
||||||
|
const b176 = (pow2(b88, _88n, P) * b88) % P;
|
||||||
|
const b220 = (pow2(b176, _44n, P) * b44) % P;
|
||||||
|
const b222 = (pow2(b220, _2n, P) * b2) % P;
|
||||||
|
const b223 = (pow2(b222, _1n, P) * x) % P;
|
||||||
|
return (pow2(b223, _223n, P) * b222) % P;
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustScalarBytes(bytes: Uint8Array): Uint8Array {
|
||||||
|
// Section 5: Likewise, for X448, set the two least significant bits of the first byte to 0, and the most
|
||||||
|
// significant bit of the last byte to 1.
|
||||||
|
bytes[0] &= 252; // 0b11111100
|
||||||
|
// and the most significant bit of the last byte to 1.
|
||||||
|
bytes[55] |= 128; // 0b10000000
|
||||||
|
// NOTE: is is NOOP for 56 bytes scalars (X25519/X448)
|
||||||
|
bytes[56] = 0; // Byte outside of group (456 buts vs 448 bits)
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ED448_DEF = {
|
||||||
|
// Param: a
|
||||||
|
a: BigInt(1),
|
||||||
|
// -39081. Negative number is P - number
|
||||||
|
d: BigInt(
|
||||||
|
'726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018326358'
|
||||||
|
),
|
||||||
|
// Finite field 𝔽p over which we'll do calculations; 2n ** 448n - 2n ** 224n - 1n
|
||||||
|
P: ed448P,
|
||||||
|
// Subgroup order: how many points ed448 has; 2n**446n - 13818066809895115352007386748515426880336692474882178609894547503885n
|
||||||
|
n: BigInt(
|
||||||
|
'181709681073901722637330951972001133588410340171829515070372549795146003961539585716195755291692375963310293709091662304773755859649779'
|
||||||
|
),
|
||||||
|
nBitLength: 456,
|
||||||
|
// Cofactor
|
||||||
|
h: BigInt(4),
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt(
|
||||||
|
'224580040295924300187604334099896036246789641632564134246125461686950415467406032909029192869357953282578032075146446173674602635247710'
|
||||||
|
),
|
||||||
|
Gy: BigInt(
|
||||||
|
'298819210078481492676017930443930673437544040154080242095928241372331506189835876003536878655418784733982303233503462500531545062832660'
|
||||||
|
),
|
||||||
|
// SHAKE256(dom4(phflag,context)||x, 114)
|
||||||
|
hash: shake256_114,
|
||||||
|
randomBytes,
|
||||||
|
adjustScalarBytes,
|
||||||
|
// dom4
|
||||||
|
domain: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => {
|
||||||
|
if (ctx.length > 255) throw new Error(`Context is too big: ${ctx.length}`);
|
||||||
|
return concatBytes(
|
||||||
|
utf8ToBytes('SigEd448'),
|
||||||
|
new Uint8Array([phflag ? 1 : 0, ctx.length]),
|
||||||
|
ctx,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
},
|
||||||
|
// Constant-time ratio of u to v. Allows to combine inversion and square root u/√v.
|
||||||
|
// Uses algo from RFC8032 5.1.3.
|
||||||
|
uvRatio: (u: bigint, v: bigint): { isValid: boolean; value: bigint } => {
|
||||||
|
const P = ed448P;
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc8032#section-5.2.3
|
||||||
|
// To compute the square root of (u/v), the first step is to compute the
|
||||||
|
// candidate root x = (u/v)^((p+1)/4). This can be done using the
|
||||||
|
// following trick, to use a single modular powering for both the
|
||||||
|
// inversion of v and the square root:
|
||||||
|
// (p+1)/4 3 (p-3)/4
|
||||||
|
// x = (u/v) = u v (u^5 v^3) (mod p)
|
||||||
|
const u2v = mod(u * u * v, P);
|
||||||
|
const u3v = mod(u2v * u, P); // u^2v
|
||||||
|
const u5v3 = mod(u3v * u2v * v, P); // u^5v^3
|
||||||
|
const root = ed448_pow_Pminus3div4(u5v3);
|
||||||
|
const x = mod(u3v * root, P);
|
||||||
|
// Verify that root is exists
|
||||||
|
const x2 = mod(x * x, P); // x^2
|
||||||
|
// If v * x^2 = u, the recovered x-coordinate is x. Otherwise, no
|
||||||
|
// square root exists, and the decoding fails.
|
||||||
|
return { isValid: mod(x2 * v, P) === u, value: x };
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const ed448 = twistedEdwards(ED448_DEF);
|
||||||
|
// NOTE: there is no ed448ctx, since ed448 supports ctx by default
|
||||||
|
export const ed448ph = twistedEdwards({ ...ED448_DEF, preHash: shake256_64 });
|
||||||
|
|
||||||
|
export const x448 = montgomery({
|
||||||
|
a24: BigInt(39081),
|
||||||
|
montgomeryBits: 448,
|
||||||
|
nByteLength: 57,
|
||||||
|
P: ed448P,
|
||||||
|
Gu: '0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
powPminus2: (x: bigint): bigint => {
|
||||||
|
const P = ed448P;
|
||||||
|
const Pminus3div4 = ed448_pow_Pminus3div4(x);
|
||||||
|
const Pminus3 = pow2(Pminus3div4, BigInt(2), P);
|
||||||
|
return mod(Pminus3 * x, P); // Pminus3 * x = Pminus2
|
||||||
|
},
|
||||||
|
adjustScalarBytes,
|
||||||
|
// The 4-isogeny maps between the Montgomery curve and this Edwards
|
||||||
|
// curve are:
|
||||||
|
// (u, v) = (y^2/x^2, (2 - x^2 - y^2)*y/x^3)
|
||||||
|
// (x, y) = (4*v*(u^2 - 1)/(u^4 - 2*u^2 + 4*v^2 + 1),
|
||||||
|
// -(u^5 - 2*u^3 - 4*u*v^2 + u)/
|
||||||
|
// (u^5 - 2*u^2*v^2 - 2*u^3 - 2*v^2 + u))
|
||||||
|
// xyToU: (p: PointType) => {
|
||||||
|
// const P = ed448P;
|
||||||
|
// const { x, y } = p;
|
||||||
|
// if (x === _0n) throw new Error(`Point with x=0 doesn't have mapping`);
|
||||||
|
// const invX = invert(x * x, P); // x^2
|
||||||
|
// const u = mod(y * y * invX, P); // (y^2/x^2)
|
||||||
|
// return numberToBytesLE(u, 56);
|
||||||
|
// },
|
||||||
|
});
|
||||||
1
curve-definitions/src/index.ts
Normal file
1
curve-definitions/src/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
throw new Error('Incorrect usage. Import submodules instead');
|
||||||
56
curve-definitions/src/jubjub.ts
Normal file
56
curve-definitions/src/jubjub.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
|
import { concatBytes, randomBytes, utf8ToBytes } from '@noble/hashes/utils';
|
||||||
|
import { twistedEdwards } from '@noble/curves/edwards';
|
||||||
|
import { blake2s } from '@noble/hashes/blake2s';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* jubjub Twisted Edwards curve.
|
||||||
|
* https://neuromancer.sk/std/other/JubJub
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const jubjub = twistedEdwards({
|
||||||
|
// Params: a, d
|
||||||
|
a: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000'),
|
||||||
|
d: BigInt('0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1'),
|
||||||
|
// Finite field 𝔽p over which we'll do calculations
|
||||||
|
P: BigInt('0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001'),
|
||||||
|
// Subgroup order: how many points ed25519 has
|
||||||
|
// 2n ** 252n + 27742317777372353535851937790883648493n;
|
||||||
|
n: BigInt('0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7'),
|
||||||
|
// Cofactor
|
||||||
|
h: BigInt(8),
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt('0x11dafe5d23e1218086a365b99fbf3d3be72f6afd7d1f72623e6b071492d1122b'),
|
||||||
|
Gy: BigInt('0x1d523cf1ddab1a1793132e78c866c0c33e26ba5cc220fed7cc3f870e59d292aa'),
|
||||||
|
hash: sha256,
|
||||||
|
randomBytes,
|
||||||
|
} as const);
|
||||||
|
|
||||||
|
const GH_FIRST_BLOCK = utf8ToBytes(
|
||||||
|
'096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns point at JubJub curve which is prime order and not zero
|
||||||
|
export function groupHash(tag: Uint8Array, personalization: Uint8Array) {
|
||||||
|
const h = blake2s.create({ personalization, dkLen: 32 });
|
||||||
|
h.update(GH_FIRST_BLOCK);
|
||||||
|
h.update(tag);
|
||||||
|
// NOTE: returns ExtendedPoint, in case it will be multiplied later
|
||||||
|
let p = jubjub.ExtendedPoint.fromAffine(jubjub.Point.fromHex(h.digest()));
|
||||||
|
// NOTE: cannot replace with isSmallOrder, returns Point*8
|
||||||
|
p = p.multiply(jubjub.CURVE.h);
|
||||||
|
if (p.equals(jubjub.ExtendedPoint.ZERO)) throw new Error('Point has small order');
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findGroupHash(m: Uint8Array, personalization: Uint8Array) {
|
||||||
|
const tag = concatBytes(m, new Uint8Array([0]));
|
||||||
|
for (let i = 0; i < 256; i++) {
|
||||||
|
tag[tag.length - 1] = i;
|
||||||
|
try {
|
||||||
|
return groupHash(tag, personalization);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
throw new Error('findGroupHash tag overflow');
|
||||||
|
}
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
||||||
import { hmac } from '@noble/hashes/hmac';
|
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
|
||||||
import { sha384, sha512 } from '@noble/hashes/sha512';
|
|
||||||
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
|
||||||
import { weierstrass, CHash } from '@noble/curves/shortw';
|
|
||||||
import { mod, pow2 } from '@noble/curves/modular';
|
|
||||||
|
|
||||||
function getHash(hash: CHash) {
|
|
||||||
return {
|
|
||||||
hash,
|
|
||||||
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(hash, key, concatBytes(...msgs)),
|
|
||||||
randomBytes,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://www.secg.org/sec2-v2.pdf
|
|
||||||
// https://neuromancer.sk/std/secg/secp192r1
|
|
||||||
export const P192 = weierstrass({
|
|
||||||
// Params: a, b
|
|
||||||
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffc'),
|
|
||||||
b: BigInt('0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1'),
|
|
||||||
// Field over which we'll do calculations. Verify with: 2n ** 192n - 2n ** 64n - 1n
|
|
||||||
P: BigInt('0xfffffffffffffffffffffffffffffffeffffffffffffffff'),
|
|
||||||
// Curve order, total count of valid points in the field. Verify with:
|
|
||||||
n: BigInt('0xffffffffffffffffffffffff99def836146bc9b1b4d22831'),
|
|
||||||
// Base point (x, y) aka generator point
|
|
||||||
Gx: BigInt('0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012'),
|
|
||||||
Gy: BigInt('0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811'),
|
|
||||||
lowS: false,
|
|
||||||
// Default options
|
|
||||||
...getHash(sha256),
|
|
||||||
} as const);
|
|
||||||
export const secp192r1 = P192;
|
|
||||||
// https://neuromancer.sk/std/nist/P-224
|
|
||||||
export const P224 = weierstrass({
|
|
||||||
// Params: a, b
|
|
||||||
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'),
|
|
||||||
b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'),
|
|
||||||
// Field over which we'll do calculations. Verify with:
|
|
||||||
P: 2n ** 224n - 2n ** 96n + 1n,
|
|
||||||
// Curve order, total count of valid points in the field. Verify with:
|
|
||||||
n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'),
|
|
||||||
// Base point (x, y) aka generator point
|
|
||||||
Gx: BigInt('0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21'),
|
|
||||||
Gy: BigInt('0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34'),
|
|
||||||
lowS: false,
|
|
||||||
// Default options
|
|
||||||
...getHash(sha256),
|
|
||||||
} as const);
|
|
||||||
export const secp224r1 = P224;
|
|
||||||
// https://neuromancer.sk/std/nist/P-256
|
|
||||||
export const P256 = weierstrass({
|
|
||||||
// Params: a, b
|
|
||||||
a: BigInt('0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc'),
|
|
||||||
b: BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b'),
|
|
||||||
// Field over which we'll do calculations. Verify with:
|
|
||||||
// 2n ** 224n * (2n ** 32n - 1n) + 2n ** 192n + 2n ** 96n - 1n,
|
|
||||||
P: BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff'),
|
|
||||||
// Curve order, total count of valid points in the field. Verify with:
|
|
||||||
n: BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'),
|
|
||||||
// Base point (x, y) aka generator point
|
|
||||||
Gx: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),
|
|
||||||
Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),
|
|
||||||
lowS: false,
|
|
||||||
// Default options
|
|
||||||
...getHash(sha256),
|
|
||||||
} as const);
|
|
||||||
export const secp256r1 = P256;
|
|
||||||
// https://neuromancer.sk/std/nist/P-384
|
|
||||||
// prettier-ignore
|
|
||||||
export const P384 = weierstrass({
|
|
||||||
// Params: a, b
|
|
||||||
a: BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc'),
|
|
||||||
b: BigInt('0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef'),
|
|
||||||
// Field over which we'll do calculations. Verify with:
|
|
||||||
// 2n ** 384n - 2n ** 128n - 2n ** 96n + 2n ** 32n - 1n
|
|
||||||
P: BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff'),
|
|
||||||
// Curve order, total count of valid points in the field. Verify with:
|
|
||||||
n: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973'),
|
|
||||||
// Base point (x, y) aka generator point
|
|
||||||
Gx: BigInt('0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7'),
|
|
||||||
Gy: BigInt('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f'),
|
|
||||||
lowS: false,
|
|
||||||
// Default options
|
|
||||||
...getHash(sha384),
|
|
||||||
} as const);
|
|
||||||
export const secp384r1 = P384;
|
|
||||||
// https://neuromancer.sk/std/nist/P-521
|
|
||||||
// prettier-ignore
|
|
||||||
export const P521 = weierstrass({
|
|
||||||
// Params: a, b
|
|
||||||
a: BigInt('0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc'),
|
|
||||||
b: BigInt('0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00'),
|
|
||||||
// Field over which we'll do calculations. Verify with:
|
|
||||||
// 2n ** 521n - 1n,
|
|
||||||
P: BigInt('0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'),
|
|
||||||
// Curve order, total count of valid points in the field. Verify with:
|
|
||||||
n: BigInt('0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409'),
|
|
||||||
// Base point (x, y) aka generator point
|
|
||||||
Gx: BigInt('0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66'),
|
|
||||||
Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'),
|
|
||||||
lowS: false,
|
|
||||||
// Default options
|
|
||||||
...getHash(sha512),
|
|
||||||
} as const);
|
|
||||||
export const secp521r1 = P521;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* secp256k1 definition with efficient square root and endomorphism.
|
|
||||||
* Endomorphism works only for Koblitz curves with a == 0.
|
|
||||||
* It improves efficiency:
|
|
||||||
* Uses 2x less RAM, speeds up precomputation by 2x and ECDH / sign key recovery by 20%.
|
|
||||||
* Should always be used for Jacobian's double-and-add multiplication.
|
|
||||||
* For affines cached multiplication, it trades off 1/2 init time & 1/3 ram for 20% perf hit.
|
|
||||||
* https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
|
|
||||||
*/
|
|
||||||
const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');
|
|
||||||
const secp256k1N = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
|
|
||||||
const _1n = BigInt(1);
|
|
||||||
const _2n = BigInt(2);
|
|
||||||
const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b;
|
|
||||||
export const secp256k1 = weierstrass({
|
|
||||||
a: 0n,
|
|
||||||
b: 7n,
|
|
||||||
// Field over which we'll do calculations. Verify with:
|
|
||||||
// 2n ** 256n - 2n ** 32n - 2n ** 9n - 2n ** 8n - 2n ** 7n - 2n ** 6n - 2n ** 4n - 1n
|
|
||||||
P: secp256k1P,
|
|
||||||
// Curve order, total count of valid points in the field. Verify with:
|
|
||||||
n: secp256k1N,
|
|
||||||
// Base point (x, y) aka generator point
|
|
||||||
Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
|
|
||||||
Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'),
|
|
||||||
...getHash(sha256),
|
|
||||||
// noble-secp256k1 compat
|
|
||||||
lowS: true,
|
|
||||||
// Used to calculate y - the square root of y².
|
|
||||||
// Exponentiates it to very big number (P+1)/4.
|
|
||||||
// We are unwrapping the loop because it's 2x faster.
|
|
||||||
// (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]
|
|
||||||
// We are multiplying it bit-by-bit
|
|
||||||
sqrtMod: (x: bigint): bigint => {
|
|
||||||
const P = secp256k1P;
|
|
||||||
const _3n = BigInt(3);
|
|
||||||
const _6n = BigInt(6);
|
|
||||||
const _11n = BigInt(11);
|
|
||||||
const _22n = BigInt(22);
|
|
||||||
const _23n = BigInt(23);
|
|
||||||
const _44n = BigInt(44);
|
|
||||||
const _88n = BigInt(88);
|
|
||||||
const b2 = (x * x * x) % P; // x^3, 11
|
|
||||||
const b3 = (b2 * b2 * x) % P; // x^7
|
|
||||||
const b6 = (pow2(b3, _3n, P) * b3) % P;
|
|
||||||
const b9 = (pow2(b6, _3n, P) * b3) % P;
|
|
||||||
const b11 = (pow2(b9, _2n, P) * b2) % P;
|
|
||||||
const b22 = (pow2(b11, _11n, P) * b11) % P;
|
|
||||||
const b44 = (pow2(b22, _22n, P) * b22) % P;
|
|
||||||
const b88 = (pow2(b44, _44n, P) * b44) % P;
|
|
||||||
const b176 = (pow2(b88, _88n, P) * b88) % P;
|
|
||||||
const b220 = (pow2(b176, _44n, P) * b44) % P;
|
|
||||||
const b223 = (pow2(b220, _3n, P) * b3) % P;
|
|
||||||
const t1 = (pow2(b223, _23n, P) * b22) % P;
|
|
||||||
const t2 = (pow2(t1, _6n, P) * b2) % P;
|
|
||||||
return pow2(t2, _2n, P);
|
|
||||||
},
|
|
||||||
endo: {
|
|
||||||
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
|
|
||||||
splitScalar: (k: bigint) => {
|
|
||||||
const n = secp256k1N;
|
|
||||||
const a1 = BigInt('0x3086d221a7d46bcde86c90e49284eb15');
|
|
||||||
const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3');
|
|
||||||
const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8');
|
|
||||||
const b2 = a1;
|
|
||||||
const POW_2_128 = BigInt('0x100000000000000000000000000000000');
|
|
||||||
|
|
||||||
const c1 = divNearest(b2 * k, n);
|
|
||||||
const c2 = divNearest(-b1 * k, n);
|
|
||||||
let k1 = mod(k - c1 * a1 - c2 * a2, n);
|
|
||||||
let k2 = mod(-c1 * b1 - c2 * b2, n);
|
|
||||||
const k1neg = k1 > POW_2_128;
|
|
||||||
const k2neg = k2 > POW_2_128;
|
|
||||||
if (k1neg) k1 = n - k1;
|
|
||||||
if (k2neg) k2 = n - k2;
|
|
||||||
if (k1 > POW_2_128 || k2 > POW_2_128) {
|
|
||||||
throw new Error('splitScalar: Endomorphism failed, k=' + k);
|
|
||||||
}
|
|
||||||
return { k1neg, k1, k2neg, k2 };
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
24
curve-definitions/src/p192.ts
Normal file
24
curve-definitions/src/p192.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
import { createCurve } from './_shortw_utils.js';
|
||||||
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
|
|
||||||
|
// NIST secp192r1 aka P192
|
||||||
|
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/secg/secp192r1
|
||||||
|
export const P192 = createCurve(
|
||||||
|
{
|
||||||
|
// Params: a, b
|
||||||
|
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffc'),
|
||||||
|
b: BigInt('0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1'),
|
||||||
|
// Field over which we'll do calculations; 2n ** 192n - 2n ** 64n - 1n
|
||||||
|
P: BigInt('0xfffffffffffffffffffffffffffffffeffffffffffffffff'),
|
||||||
|
// Curve order, total count of valid points in the field.
|
||||||
|
n: BigInt('0xffffffffffffffffffffffff99def836146bc9b1b4d22831'),
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt('0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012'),
|
||||||
|
Gy: BigInt('0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811'),
|
||||||
|
h: BigInt(1),
|
||||||
|
lowS: false,
|
||||||
|
} as const,
|
||||||
|
sha256
|
||||||
|
);
|
||||||
|
export const secp192r1 = P192;
|
||||||
24
curve-definitions/src/p224.ts
Normal file
24
curve-definitions/src/p224.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
import { createCurve } from './_shortw_utils.js';
|
||||||
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
|
|
||||||
|
// NIST secp224r1 aka P224
|
||||||
|
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-224
|
||||||
|
export const P224 = createCurve(
|
||||||
|
{
|
||||||
|
// Params: a, b
|
||||||
|
a: BigInt('0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe'),
|
||||||
|
b: BigInt('0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4'),
|
||||||
|
// Field over which we'll do calculations; 2n**224n - 2n**96n + 1n
|
||||||
|
P: BigInt('0xffffffffffffffffffffffffffffffff000000000000000000000001'),
|
||||||
|
// Curve order, total count of valid points in the field
|
||||||
|
n: BigInt('0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d'),
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt('0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21'),
|
||||||
|
Gy: BigInt('0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34'),
|
||||||
|
h: BigInt(1),
|
||||||
|
lowS: false,
|
||||||
|
} as const,
|
||||||
|
sha256 // TODO: replace with sha224 when new @noble/hashes released
|
||||||
|
);
|
||||||
|
export const secp224r1 = P224;
|
||||||
24
curve-definitions/src/p256.ts
Normal file
24
curve-definitions/src/p256.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
import { createCurve } from './_shortw_utils.js';
|
||||||
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
|
|
||||||
|
// NIST secp256r1 aka P256
|
||||||
|
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-256
|
||||||
|
export const P256 = createCurve(
|
||||||
|
{
|
||||||
|
// Params: a, b
|
||||||
|
a: BigInt('0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc'),
|
||||||
|
b: BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b'),
|
||||||
|
// Field over which we'll do calculations; 2n**224n * (2n**32n-1n) + 2n**192n + 2n**96n-1n
|
||||||
|
P: BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff'),
|
||||||
|
// Curve order, total count of valid points in the field
|
||||||
|
n: BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'),
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'),
|
||||||
|
Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'),
|
||||||
|
h: BigInt(1),
|
||||||
|
lowS: false,
|
||||||
|
} as const,
|
||||||
|
sha256
|
||||||
|
);
|
||||||
|
export const secp256r1 = P256;
|
||||||
22
curve-definitions/src/p384.ts
Normal file
22
curve-definitions/src/p384.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
import { createCurve } from './_shortw_utils.js';
|
||||||
|
import { sha384 } from '@noble/hashes/sha512';
|
||||||
|
|
||||||
|
// NIST secp384r1 aka P384
|
||||||
|
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-384
|
||||||
|
// prettier-ignore
|
||||||
|
export const P384 = createCurve({
|
||||||
|
// Params: a, b
|
||||||
|
a: BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc'),
|
||||||
|
b: BigInt('0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef'),
|
||||||
|
// Field over which we'll do calculations. 2n**384n - 2n**128n - 2n**96n + 2n**32n - 1n
|
||||||
|
P: BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff'),
|
||||||
|
// Curve order, total count of valid points in the field.
|
||||||
|
n: BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973'),
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt('0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7'),
|
||||||
|
Gy: BigInt('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f'),
|
||||||
|
h: BigInt(1),
|
||||||
|
lowS: false,
|
||||||
|
} as const, sha384);
|
||||||
|
export const secp384r1 = P384;
|
||||||
23
curve-definitions/src/p521.ts
Normal file
23
curve-definitions/src/p521.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
import { createCurve } from './_shortw_utils.js';
|
||||||
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
|
|
||||||
|
// NIST secp521r1 aka P521
|
||||||
|
// Note that it's 521, which differs from 512 of its hash function.
|
||||||
|
// https://www.secg.org/sec2-v2.pdf, https://neuromancer.sk/std/nist/P-521
|
||||||
|
// prettier-ignore
|
||||||
|
export const P521 = createCurve({
|
||||||
|
// Params: a, b
|
||||||
|
a: BigInt('0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc'),
|
||||||
|
b: BigInt('0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00'),
|
||||||
|
// Field over which we'll do calculations; 2n**521n - 1n
|
||||||
|
P: BigInt('0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'),
|
||||||
|
// Curve order, total count of valid points in the field
|
||||||
|
n: BigInt('0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409'),
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt('0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66'),
|
||||||
|
Gy: BigInt('0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650'),
|
||||||
|
h: BigInt(1),
|
||||||
|
lowS: false,
|
||||||
|
} as const, sha512);
|
||||||
|
export const secp521r1 = P521;
|
||||||
@@ -1,35 +1,31 @@
|
|||||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
import { hmac } from '@noble/hashes/hmac';
|
|
||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
import { weierstrass } from '@noble/curves/weierstrass';
|
||||||
import { weierstrass, CHash } from '@noble/curves/shortw';
|
import { getHash } from './_shortw_utils.js';
|
||||||
|
import * as mod from '@noble/curves/modular';
|
||||||
|
|
||||||
function getHash(hash: CHash) {
|
|
||||||
return {
|
|
||||||
hash,
|
|
||||||
hmac: (key: Uint8Array, ...msgs: Uint8Array[]) => hmac(hash, key, concatBytes(...msgs)),
|
|
||||||
randomBytes,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const p = BigInt('0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001');
|
const p = BigInt('0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001');
|
||||||
const q = BigInt('0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001');
|
const q = BigInt('0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001');
|
||||||
|
|
||||||
|
// https://neuromancer.sk/std/other/Pallas
|
||||||
export const pallas = weierstrass({
|
export const pallas = weierstrass({
|
||||||
a: BigInt(0),
|
a: BigInt(0),
|
||||||
b: BigInt(5),
|
b: BigInt(5),
|
||||||
P: p,
|
P: p,
|
||||||
n: q,
|
n: q,
|
||||||
Gx: BigInt(-1),
|
Gx: mod.mod(BigInt(-1), p),
|
||||||
Gy: BigInt(2),
|
Gy: BigInt(2),
|
||||||
|
h: BigInt(1),
|
||||||
...getHash(sha256),
|
...getHash(sha256),
|
||||||
});
|
});
|
||||||
|
// https://neuromancer.sk/std/other/Vesta
|
||||||
export const vesta = weierstrass({
|
export const vesta = weierstrass({
|
||||||
a: BigInt(0),
|
a: BigInt(0),
|
||||||
b: BigInt(5),
|
b: BigInt(5),
|
||||||
P: q,
|
P: q,
|
||||||
n: p,
|
n: p,
|
||||||
Gx: BigInt(-1),
|
Gx: mod.mod(BigInt(-1), q),
|
||||||
Gy: BigInt(2),
|
Gy: BigInt(2),
|
||||||
|
h: BigInt(1),
|
||||||
...getHash(sha256),
|
...getHash(sha256),
|
||||||
});
|
});
|
||||||
|
|||||||
262
curve-definitions/src/secp256k1.ts
Normal file
262
curve-definitions/src/secp256k1.ts
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
|
import { mod, pow2 } from '@noble/curves/modular';
|
||||||
|
import { createCurve } from './_shortw_utils.js';
|
||||||
|
import { PointType } from '@noble/curves/weierstrass';
|
||||||
|
import {
|
||||||
|
ensureBytes,
|
||||||
|
concatBytes,
|
||||||
|
Hex,
|
||||||
|
hexToBytes,
|
||||||
|
bytesToNumberBE,
|
||||||
|
PrivKey,
|
||||||
|
} from '@noble/curves/utils';
|
||||||
|
import { randomBytes } from '@noble/hashes/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* secp256k1 belongs to Koblitz curves: it has
|
||||||
|
* efficiently computable Frobenius endomorphism.
|
||||||
|
* Endomorphism improves efficiency:
|
||||||
|
* Uses 2x less RAM, speeds up precomputation by 2x and ECDH / sign key recovery by 20%.
|
||||||
|
* Should always be used for Jacobian's double-and-add multiplication.
|
||||||
|
* For affines cached multiplication, it trades off 1/2 init time & 1/3 ram for 20% perf hit.
|
||||||
|
* https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
|
||||||
|
*/
|
||||||
|
|
||||||
|
const secp256k1P = BigInt('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f');
|
||||||
|
const secp256k1N = BigInt('0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141');
|
||||||
|
const _1n = BigInt(1);
|
||||||
|
const _2n = BigInt(2);
|
||||||
|
const divNearest = (a: bigint, b: bigint) => (a + b / _2n) / b;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to compute square root √y 2x faster.
|
||||||
|
* To calculate √y, we need to exponentiate it to a very big number:
|
||||||
|
* `y² = x³ + ax + b; y = y² ^ (p+1)/4`
|
||||||
|
* We are unwrapping the loop and multiplying it bit-by-bit.
|
||||||
|
* (P+1n/4n).toString(2) would produce bits [223x 1, 0, 22x 1, 4x 0, 11, 00]
|
||||||
|
*/
|
||||||
|
// prettier-ignore
|
||||||
|
function sqrtMod(y: bigint): bigint {
|
||||||
|
const P = secp256k1P;
|
||||||
|
const _3n = BigInt(3), _6n = BigInt(6), _11n = BigInt(11); const _22n = BigInt(22);
|
||||||
|
const _23n = BigInt(23), _44n = BigInt(44), _88n = BigInt(88);
|
||||||
|
const b2 = (y * y * y) % P; // x^3, 11
|
||||||
|
const b3 = (b2 * b2 * y) % P; // x^7
|
||||||
|
const b6 = (pow2(b3, _3n, P) * b3) % P;
|
||||||
|
const b9 = (pow2(b6, _3n, P) * b3) % P;
|
||||||
|
const b11 = (pow2(b9, _2n, P) * b2) % P;
|
||||||
|
const b22 = (pow2(b11, _11n, P) * b11) % P;
|
||||||
|
const b44 = (pow2(b22, _22n, P) * b22) % P;
|
||||||
|
const b88 = (pow2(b44, _44n, P) * b44) % P;
|
||||||
|
const b176 = (pow2(b88, _88n, P) * b88) % P;
|
||||||
|
const b220 = (pow2(b176, _44n, P) * b44) % P;
|
||||||
|
const b223 = (pow2(b220, _3n, P) * b3) % P;
|
||||||
|
const t1 = (pow2(b223, _23n, P) * b22) % P;
|
||||||
|
const t2 = (pow2(t1, _6n, P) * b2) % P;
|
||||||
|
return pow2(t2, _2n, P);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const secp256k1 = createCurve(
|
||||||
|
{
|
||||||
|
// Params: a, b
|
||||||
|
// Seem to be rigid https://bitcointalk.org/index.php?topic=289795.msg3183975#msg3183975
|
||||||
|
a: BigInt(0),
|
||||||
|
b: BigInt(7),
|
||||||
|
// Field over which we'll do calculations;
|
||||||
|
// 2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n
|
||||||
|
P: secp256k1P,
|
||||||
|
// Curve order, total count of valid points in the field
|
||||||
|
n: secp256k1N,
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
|
||||||
|
Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'),
|
||||||
|
h: BigInt(1),
|
||||||
|
// Alllow only low-S signatures by default in sign() and verify()
|
||||||
|
lowS: true,
|
||||||
|
sqrtMod,
|
||||||
|
endo: {
|
||||||
|
// Params taken from https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066
|
||||||
|
beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'),
|
||||||
|
splitScalar: (k: bigint) => {
|
||||||
|
const n = secp256k1N;
|
||||||
|
const a1 = BigInt('0x3086d221a7d46bcde86c90e49284eb15');
|
||||||
|
const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3');
|
||||||
|
const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8');
|
||||||
|
const b2 = a1;
|
||||||
|
const POW_2_128 = BigInt('0x100000000000000000000000000000000');
|
||||||
|
|
||||||
|
const c1 = divNearest(b2 * k, n);
|
||||||
|
const c2 = divNearest(-b1 * k, n);
|
||||||
|
let k1 = mod(k - c1 * a1 - c2 * a2, n);
|
||||||
|
let k2 = mod(-c1 * b1 - c2 * b2, n);
|
||||||
|
const k1neg = k1 > POW_2_128;
|
||||||
|
const k2neg = k2 > POW_2_128;
|
||||||
|
if (k1neg) k1 = n - k1;
|
||||||
|
if (k2neg) k2 = n - k2;
|
||||||
|
if (k1 > POW_2_128 || k2 > POW_2_128) {
|
||||||
|
throw new Error('splitScalar: Endomorphism failed, k=' + k);
|
||||||
|
}
|
||||||
|
return { k1neg, k1, k2neg, k2 };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sha256
|
||||||
|
);
|
||||||
|
|
||||||
|
// Schnorr
|
||||||
|
const _0n = BigInt(0);
|
||||||
|
const numTo32b = secp256k1.utils._bigintToBytes;
|
||||||
|
const numTo32bStr = secp256k1.utils._bigintToString;
|
||||||
|
const normalizePrivateKey = secp256k1.utils._normalizePrivateKey;
|
||||||
|
|
||||||
|
// TODO: export?
|
||||||
|
function normalizePublicKey(publicKey: Hex | PointType): PointType {
|
||||||
|
if (publicKey instanceof secp256k1.Point) {
|
||||||
|
publicKey.assertValidity();
|
||||||
|
return publicKey;
|
||||||
|
} else {
|
||||||
|
const bytes = ensureBytes(publicKey);
|
||||||
|
// Schnorr is 32 bytes
|
||||||
|
if (bytes.length === 32) {
|
||||||
|
const x = bytesToNumberBE(bytes);
|
||||||
|
if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
|
||||||
|
const y2 = secp256k1.utils._weierstrassEquation(x); // y² = x³ + ax + b
|
||||||
|
let y = sqrtMod(y2); // y = y² ^ (p+1)/4
|
||||||
|
const isYOdd = (y & _1n) === _1n;
|
||||||
|
// Schnorr
|
||||||
|
if (isYOdd) y = mod(-y, secp256k1.CURVE.P);
|
||||||
|
const point = new secp256k1.Point(x, y);
|
||||||
|
point.assertValidity();
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
// Do we need that in schnorr at all?
|
||||||
|
return secp256k1.Point.fromHex(publicKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isWithinCurveOrder = secp256k1.utils._isWithinCurveOrder;
|
||||||
|
const isValidFieldElement = secp256k1.utils._isValidFieldElement;
|
||||||
|
|
||||||
|
const TAGS = {
|
||||||
|
challenge: 'BIP0340/challenge',
|
||||||
|
aux: 'BIP0340/aux',
|
||||||
|
nonce: 'BIP0340/nonce',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/** An object mapping tags to their tagged hash prefix of [SHA256(tag) | SHA256(tag)] */
|
||||||
|
const TAGGED_HASH_PREFIXES: { [tag: string]: Uint8Array } = {};
|
||||||
|
export function taggedHash(tag: string, ...messages: Uint8Array[]): Uint8Array {
|
||||||
|
let tagP = TAGGED_HASH_PREFIXES[tag];
|
||||||
|
if (tagP === undefined) {
|
||||||
|
const tagH = sha256(Uint8Array.from(tag, (c) => c.charCodeAt(0)));
|
||||||
|
tagP = concatBytes(tagH, tagH);
|
||||||
|
TAGGED_HASH_PREFIXES[tag] = tagP;
|
||||||
|
}
|
||||||
|
return sha256(concatBytes(tagP, ...messages));
|
||||||
|
}
|
||||||
|
|
||||||
|
const toRawX = (point: PointType) => point.toRawBytes(true).slice(1);
|
||||||
|
|
||||||
|
// Schnorr signatures are superior to ECDSA from above.
|
||||||
|
// Below is Schnorr-specific code as per BIP0340.
|
||||||
|
function schnorrChallengeFinalize(ch: Uint8Array): bigint {
|
||||||
|
return mod(bytesToNumberBE(ch), secp256k1.CURVE.n);
|
||||||
|
}
|
||||||
|
// Do we need this at all for Schnorr?
|
||||||
|
class SchnorrSignature {
|
||||||
|
constructor(readonly r: bigint, readonly s: bigint) {
|
||||||
|
this.assertValidity();
|
||||||
|
}
|
||||||
|
static fromHex(hex: Hex) {
|
||||||
|
const bytes = ensureBytes(hex);
|
||||||
|
if (bytes.length !== 64)
|
||||||
|
throw new TypeError(`SchnorrSignature.fromHex: expected 64 bytes, not ${bytes.length}`);
|
||||||
|
const r = bytesToNumberBE(bytes.subarray(0, 32));
|
||||||
|
const s = bytesToNumberBE(bytes.subarray(32, 64));
|
||||||
|
return new SchnorrSignature(r, s);
|
||||||
|
}
|
||||||
|
assertValidity() {
|
||||||
|
const { r, s } = this;
|
||||||
|
if (!isValidFieldElement(r) || !isWithinCurveOrder(s)) throw new Error('Invalid signature');
|
||||||
|
}
|
||||||
|
toHex(): string {
|
||||||
|
return numTo32bStr(this.r) + numTo32bStr(this.s);
|
||||||
|
}
|
||||||
|
toRawBytes(): Uint8Array {
|
||||||
|
return hexToBytes(this.toHex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function schnorrGetScalar(priv: bigint) {
|
||||||
|
const point = secp256k1.Point.fromPrivateKey(priv);
|
||||||
|
const scalar = point.hasEvenY() ? priv : secp256k1.CURVE.n - priv;
|
||||||
|
return { point, scalar, x: toRawX(point) };
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Synchronously creates Schnorr signature. Improved security: verifies itself before
|
||||||
|
* producing an output.
|
||||||
|
* @param msg message (not message hash)
|
||||||
|
* @param privateKey private key
|
||||||
|
* @param auxRand random bytes that would be added to k. Bad RNG won't break it.
|
||||||
|
*/
|
||||||
|
function schnorrSign(
|
||||||
|
message: Hex,
|
||||||
|
privateKey: PrivKey,
|
||||||
|
auxRand: Hex = randomBytes(32)
|
||||||
|
): Uint8Array {
|
||||||
|
if (message == null) throw new TypeError(`sign: Expected valid message, not "${message}"`);
|
||||||
|
const m = ensureBytes(message);
|
||||||
|
// checks for isWithinCurveOrder
|
||||||
|
const { x: px, scalar: d } = schnorrGetScalar(normalizePrivateKey(privateKey));
|
||||||
|
const rand = ensureBytes(auxRand);
|
||||||
|
if (rand.length !== 32) throw new TypeError('sign: Expected 32 bytes of aux randomness');
|
||||||
|
const tag = taggedHash;
|
||||||
|
const t0h = tag(TAGS.aux, rand);
|
||||||
|
const t = numTo32b(d ^ bytesToNumberBE(t0h));
|
||||||
|
const k0h = tag(TAGS.nonce, t, px, m);
|
||||||
|
const k0 = mod(bytesToNumberBE(k0h), secp256k1.CURVE.n);
|
||||||
|
if (k0 === _0n) throw new Error('sign: Creation of signature failed. k is zero');
|
||||||
|
const { point: R, x: rx, scalar: k } = schnorrGetScalar(k0);
|
||||||
|
const e = schnorrChallengeFinalize(tag(TAGS.challenge, rx, px, m));
|
||||||
|
const sig = new SchnorrSignature(R.x, mod(k + e * d, secp256k1.CURVE.n)).toRawBytes();
|
||||||
|
if (!schnorrVerify(sig, m, px)) throw new Error('sign: Invalid signature produced');
|
||||||
|
return sig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies Schnorr signature synchronously.
|
||||||
|
*/
|
||||||
|
function schnorrVerify(signature: Hex, message: Hex, publicKey: Hex): boolean {
|
||||||
|
try {
|
||||||
|
const raw = signature instanceof SchnorrSignature;
|
||||||
|
const sig: SchnorrSignature = raw ? signature : SchnorrSignature.fromHex(signature);
|
||||||
|
if (raw) sig.assertValidity(); // just in case
|
||||||
|
|
||||||
|
const { r, s } = sig;
|
||||||
|
const m = ensureBytes(message);
|
||||||
|
const P = normalizePublicKey(publicKey);
|
||||||
|
const e = schnorrChallengeFinalize(taggedHash(TAGS.challenge, numTo32b(r), toRawX(P), m));
|
||||||
|
// Finalize
|
||||||
|
// R = s⋅G - e⋅P
|
||||||
|
// -eP == (n-e)P
|
||||||
|
const R = secp256k1.Point.BASE.multiplyAndAddUnsafe(
|
||||||
|
P,
|
||||||
|
normalizePrivateKey(s),
|
||||||
|
mod(-e, secp256k1.CURVE.n)
|
||||||
|
);
|
||||||
|
if (!R || !R.hasEvenY() || R.x !== r) return false;
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const schnorr = {
|
||||||
|
Signature: SchnorrSignature,
|
||||||
|
// Schnorr's pubkey is just `x` of Point (BIP340)
|
||||||
|
getPublicKey: (privateKey: PrivKey): Uint8Array =>
|
||||||
|
toRawX(secp256k1.Point.fromPrivateKey(privateKey)),
|
||||||
|
sign: schnorrSign,
|
||||||
|
verify: schnorrVerify,
|
||||||
|
};
|
||||||
@@ -3,9 +3,12 @@ import { keccak_256 } from '@noble/hashes/sha3';
|
|||||||
import { sha256 } from '@noble/hashes/sha256';
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
import { hmac } from '@noble/hashes/hmac';
|
import { hmac } from '@noble/hashes/hmac';
|
||||||
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
import { concatBytes, randomBytes } from '@noble/hashes/utils';
|
||||||
import { weierstrass, CHash, JacobianPointType } from '@noble/curves/shortw';
|
import { weierstrass, CHash, JacobianPointType } from '@noble/curves/weierstrass';
|
||||||
import * as cutils from '@noble/curves/utils';
|
import * as cutils from '@noble/curves/utils';
|
||||||
|
|
||||||
|
// Stark-friendly elliptic curve
|
||||||
|
// https://docs.starkware.co/starkex/stark-curve.html
|
||||||
|
|
||||||
function getHash(hash: CHash) {
|
function getHash(hash: CHash) {
|
||||||
return {
|
return {
|
||||||
hash,
|
hash,
|
||||||
@@ -14,22 +17,24 @@ function getHash(hash: CHash) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const CURVE_N = 3618502788666131213697322783095070105526743751716087489154079457884512865583n;
|
const CURVE_N = BigInt(
|
||||||
|
'3618502788666131213697322783095070105526743751716087489154079457884512865583'
|
||||||
|
);
|
||||||
const nBitLength = 252;
|
const nBitLength = 252;
|
||||||
// https://docs.starkware.co/starkex/stark-curve.html
|
|
||||||
export const starkCurve = weierstrass({
|
export const starkCurve = weierstrass({
|
||||||
// Params: a, b
|
// Params: a, b
|
||||||
a: 1n,
|
a: BigInt(1),
|
||||||
b: 3141592653589793238462643383279502884197169399375105820974944592307816406665n,
|
b: BigInt('3141592653589793238462643383279502884197169399375105820974944592307816406665'),
|
||||||
// Field over which we'll do calculations. Verify with:
|
// Field over which we'll do calculations; 2n**251n + 17n * 2n**192n + 1n
|
||||||
// NOTE: there is no efficient sqrt for field (P%4==1)
|
// There is no efficient sqrt for field (P%4==1)
|
||||||
P: 2n ** 251n + 17n * 2n ** 192n + 1n,
|
P: BigInt('0x800000000000011000000000000000000000000000000000000000000000001'),
|
||||||
// Curve order, total count of valid points in the field. Verify with:
|
// Curve order, total count of valid points in the field.
|
||||||
n: CURVE_N,
|
n: CURVE_N,
|
||||||
nBitLength: nBitLength, // len(bin(N).replace('0b',''))
|
nBitLength: nBitLength, // len(bin(N).replace('0b',''))
|
||||||
// Base point (x, y) aka generator point
|
// Base point (x, y) aka generator point
|
||||||
Gx: 874739451078007766457464989774322083649278607533249481151382481072868806602n,
|
Gx: BigInt('874739451078007766457464989774322083649278607533249481151382481072868806602'),
|
||||||
Gy: 152666792071518830868575557812948353041420400780739481342941381225525861407n,
|
Gy: BigInt('152666792071518830868575557812948353041420400780739481342941381225525861407'),
|
||||||
|
h: BigInt(1),
|
||||||
// Default options
|
// Default options
|
||||||
lowS: false,
|
lowS: false,
|
||||||
...getHash(sha256),
|
...getHash(sha256),
|
||||||
@@ -1,95 +1,315 @@
|
|||||||
import { deepStrictEqual, throws } from 'assert';
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
import { should } from 'micro-should';
|
import { should } from 'micro-should';
|
||||||
import * as nist from '../lib/nist.js';
|
import * as fc from 'fast-check';
|
||||||
import { hexToBytes } from '@noble/curves/utils';
|
import * as mod from '@noble/curves/modular';
|
||||||
import { default as ecdsa } from './fixtures/ecdsa_test.json' assert { type: 'json' };
|
import { randomBytes } from '@noble/hashes/utils';
|
||||||
import { default as ecdh } from './fixtures/ecdh_test.json' assert { type: 'json' };
|
// Generic tests for all curves in package
|
||||||
|
import { secp192r1 } from '../lib/p192.js';
|
||||||
|
import { secp224r1 } from '../lib/p224.js';
|
||||||
|
import { secp256r1 } from '../lib/p256.js';
|
||||||
|
import { secp384r1 } from '../lib/p384.js';
|
||||||
|
import { secp521r1 } from '../lib/p521.js';
|
||||||
|
import { secp256k1 } from '../lib/secp256k1.js';
|
||||||
|
import { ed25519, ed25519ctx, ed25519ph } from '../lib/ed25519.js';
|
||||||
|
import { ed448, ed448ph } from '../lib/ed448.js';
|
||||||
|
import { starkCurve } from '../lib/stark.js';
|
||||||
|
import { pallas, vesta } from '../lib/pasta.js';
|
||||||
|
import { bn254 } from '../lib/bn.js';
|
||||||
|
import { jubjub } from '../lib/jubjub.js';
|
||||||
|
|
||||||
// import { hexToBytes } from '@noble/curves';
|
// prettier-ignore
|
||||||
|
const CURVES = {
|
||||||
|
secp192r1, secp224r1, secp256r1, secp384r1, secp521r1,
|
||||||
|
secp256k1,
|
||||||
|
ed25519, ed25519ctx, ed25519ph,
|
||||||
|
ed448, ed448ph,
|
||||||
|
starkCurve,
|
||||||
|
pallas, vesta,
|
||||||
|
bn254,
|
||||||
|
jubjub,
|
||||||
|
};
|
||||||
|
|
||||||
should('Curve Fields', () => {
|
const NUM_RUNS = 5;
|
||||||
const vectors = {
|
const getXY = (p) => ({ x: p.x, y: p.y });
|
||||||
secp192r1: 0xfffffffffffffffffffffffffffffffeffffffffffffffffn,
|
|
||||||
secp224r1: 0xffffffffffffffffffffffffffffffff000000000000000000000001n,
|
|
||||||
secp256r1: 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffffn,
|
|
||||||
secp256k1: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn,
|
|
||||||
secp384r1:
|
|
||||||
0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffffn,
|
|
||||||
secp521r1:
|
|
||||||
0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn,
|
|
||||||
};
|
|
||||||
for (const n in vectors) deepStrictEqual(nist[n].CURVE.P, vectors[n]);
|
|
||||||
});
|
|
||||||
|
|
||||||
should('wychenproof ECDSA vectors', () => {
|
function equal(a, b, comment) {
|
||||||
for (const group of ecdsa.testGroups) {
|
deepStrictEqual(a.equals(b), true, `eq(${comment})`);
|
||||||
// Tested in secp256k1.test.js
|
if (a.toAffine && b.toAffine) {
|
||||||
if (group.key.curve === 'secp256k1') continue;
|
deepStrictEqual(getXY(a.toAffine()), getXY(b.toAffine()), `eqToAffine(${comment})`);
|
||||||
// We don't have SHA-224
|
} else if (!a.toAffine && !b.toAffine) {
|
||||||
if (group.key.curve === 'secp224r1' && group.sha === 'SHA-224') continue;
|
// Already affine
|
||||||
const CURVE = nist[group.key.curve];
|
deepStrictEqual(getXY(a), getXY(b), `eqAffine(${comment})`);
|
||||||
if (!CURVE) continue;
|
} else throw new Error('Different point types');
|
||||||
const pubKey = CURVE.Point.fromHex(group.key.uncompressed);
|
}
|
||||||
deepStrictEqual(pubKey.x, BigInt(`0x${group.key.wx}`));
|
|
||||||
deepStrictEqual(pubKey.y, BigInt(`0x${group.key.wy}`));
|
for (const name in CURVES) {
|
||||||
for (const test of group.tests) {
|
const C = CURVES[name];
|
||||||
if (['Hash weaker than DL-group'].includes(test.comment)) {
|
const CURVE_ORDER = C.CURVE.n;
|
||||||
continue;
|
const FC_BIGINT = fc.bigInt(1n + 1n, CURVE_ORDER - 1n);
|
||||||
|
|
||||||
|
// Check that curve doesn't accept points from other curves
|
||||||
|
const O = name === 'secp256k1' ? secp256r1 : secp256k1;
|
||||||
|
const POINTS = {};
|
||||||
|
const OTHER_POINTS = {};
|
||||||
|
for (const name of ['Point', 'JacobianPoint', 'ExtendedPoint', 'ProjectivePoint']) {
|
||||||
|
POINTS[name] = C[name];
|
||||||
|
OTHER_POINTS[name] = O[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const pointName in POINTS) {
|
||||||
|
const p = POINTS[pointName];
|
||||||
|
const o = OTHER_POINTS[pointName];
|
||||||
|
if (!p) continue;
|
||||||
|
|
||||||
|
const G = [p.ZERO, p.BASE];
|
||||||
|
for (let i = 2; i < 10; i++) G.push(G[1].multiply(i));
|
||||||
|
// Here we check basic group laws, to verify that points works as group
|
||||||
|
should(`${name}/${pointName}/Basic group laws (zero)`, () => {
|
||||||
|
equal(G[0].double(), G[0], '(0*G).double() = 0');
|
||||||
|
equal(G[0].add(G[0]), G[0], '0*G + 0*G = 0');
|
||||||
|
equal(G[0].subtract(G[0]), G[0], '0*G - 0*G = 0');
|
||||||
|
equal(G[0].negate(), G[0], '-0 = 0');
|
||||||
|
for (let i = 0; i < G.length; i++) {
|
||||||
|
const p = G[i];
|
||||||
|
equal(p, p.add(G[0]), `${i}*G + 0 = ${i}*G`);
|
||||||
|
equal(G[0].multiply(i + 1), G[0], `${i + 1}*0 = 0`);
|
||||||
}
|
}
|
||||||
const m = CURVE.CURVE.hash(hexToBytes(test.msg));
|
});
|
||||||
if (test.result === 'valid' || test.result === 'acceptable') {
|
should(`${name}/${pointName}/Basic group laws (one)`, () => {
|
||||||
try {
|
equal(G[1].double(), G[2], '(1*G).double() = 2*G');
|
||||||
CURVE.Signature.fromDER(test.sig);
|
equal(G[1].subtract(G[1]), G[0], '1*G - 1*G = 0');
|
||||||
} catch (e) {
|
equal(G[1].add(G[1]), G[2], '1*G + 1*G = 2*G');
|
||||||
// Some test has invalid signature which we don't accept
|
});
|
||||||
if (e.message.includes('Invalid signature: incorrect length')) continue;
|
should(`${name}/${pointName}/Basic group laws (sanity tests)`, () => {
|
||||||
throw e;
|
equal(G[2].double(), G[4], `(2*G).double() = 4*G`);
|
||||||
|
equal(G[2].add(G[2]), G[4], `2*G + 2*G = 4*G`);
|
||||||
|
equal(G[7].add(G[3].negate()), G[4], `7*G - 3*G = 4*G`);
|
||||||
|
});
|
||||||
|
should(`${name}/${pointName}/Basic group laws (addition commutativity)`, () => {
|
||||||
|
equal(G[4].add(G[3]), G[3].add(G[4]), `4*G + 3*G = 3*G + 4*G`);
|
||||||
|
equal(G[4].add(G[3]), G[3].add(G[2]).add(G[2]), `4*G + 3*G = 3*G + 2*G + 2*G`);
|
||||||
|
});
|
||||||
|
should(`${name}/${pointName}/Basic group laws (double)`, () => {
|
||||||
|
equal(G[3].double(), G[6], '(3*G).double() = 6*G');
|
||||||
|
});
|
||||||
|
should(`${name}/${pointName}/Basic group laws (multiply)`, () => {
|
||||||
|
equal(G[2].multiply(3), G[6], '(2*G).multiply(3) = 6*G');
|
||||||
|
});
|
||||||
|
should(`${name}/${pointName}/Basic group laws (same point addition)`, () => {
|
||||||
|
equal(G[3].add(G[3]), G[6], `3*G + 3*G = 6*G`);
|
||||||
|
});
|
||||||
|
should(`${name}/${pointName}/Basic group laws (same point (negative) addition)`, () => {
|
||||||
|
equal(G[3].add(G[3].negate()), G[0], '3*G + (- 3*G) = 0*G');
|
||||||
|
equal(G[3].subtract(G[3]), G[0], '3*G - 3*G = 0*G');
|
||||||
|
});
|
||||||
|
should(`${name}/${pointName}/Basic group laws (curve order)`, () => {
|
||||||
|
equal(G[1].multiply(CURVE_ORDER - 1n).add(G[1]), G[0], '(N-1)*G + G = 0');
|
||||||
|
equal(G[1].multiply(CURVE_ORDER - 1n).add(G[2]), G[1], '(N-1)*G + 2*G = 1*G');
|
||||||
|
equal(G[1].multiply(CURVE_ORDER - 2n).add(G[2]), G[0], '(N-2)*G + 2*G = 0');
|
||||||
|
const half = CURVE_ORDER / 2n;
|
||||||
|
const carry = CURVE_ORDER % 2n === 1n ? G[1] : G[0];
|
||||||
|
equal(G[1].multiply(half).double().add(carry), G[0], '((N/2) * G).double() = 0');
|
||||||
|
});
|
||||||
|
should(`${name}/${pointName}/Basic group laws (inversion)`, () => {
|
||||||
|
const a = 1234n;
|
||||||
|
const b = 5678n;
|
||||||
|
const c = a * b;
|
||||||
|
equal(G[1].multiply(a).multiply(b), G[1].multiply(c), 'a*b*G = c*G');
|
||||||
|
const inv = mod.invert(b, CURVE_ORDER);
|
||||||
|
equal(G[1].multiply(c).multiply(inv), G[1].multiply(a), 'c*G * (1/b)*G = a*G');
|
||||||
|
});
|
||||||
|
should(`${name}/${pointName}/Basic group laws (multiply, rand)`, () =>
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
|
||||||
|
const c = mod.mod(a + b, CURVE_ORDER);
|
||||||
|
if (c === CURVE_ORDER || c < 1n) return;
|
||||||
|
const pA = G[1].multiply(a);
|
||||||
|
const pB = G[1].multiply(b);
|
||||||
|
const pC = G[1].multiply(c);
|
||||||
|
equal(pA.add(pB), pB.add(pA), `pA + pB = pB + pA`);
|
||||||
|
equal(pA.add(pB), pC, `pA + pB = pC`);
|
||||||
|
}),
|
||||||
|
{ numRuns: NUM_RUNS }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
should(`${name}/${pointName}/Basic group laws (multiply2, rand)`, () =>
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, FC_BIGINT, (a, b) => {
|
||||||
|
const c = mod.mod(a * b, CURVE_ORDER);
|
||||||
|
const pA = G[1].multiply(a);
|
||||||
|
const pB = G[1].multiply(b);
|
||||||
|
equal(pA.multiply(b), pB.multiply(a), `b*pA = a*pB`);
|
||||||
|
equal(pA.multiply(b), G[1].multiply(c), `b*pA = c*G`);
|
||||||
|
}),
|
||||||
|
{ numRuns: NUM_RUNS }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const op of ['add', 'subtract']) {
|
||||||
|
should(`${name}/${pointName}/${op} type check`, () => {
|
||||||
|
throws(() => G[1][op](0), '0');
|
||||||
|
throws(() => G[1][op](0n), '0n');
|
||||||
|
G[1][op](G[2]);
|
||||||
|
throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER');
|
||||||
|
throws(() => G[1][op](123.456), '123.456');
|
||||||
|
throws(() => G[1][op](true), 'true');
|
||||||
|
throws(() => G[1][op]('1'), "'1'");
|
||||||
|
throws(() => G[1][op]({ x: 1n, y: 1n, z: 1n, t: 1n }), '{ x: 1n, y: 1n, z: 1n, t: 1n }');
|
||||||
|
throws(() => G[1][op](new Uint8Array([])), 'ui8a([])');
|
||||||
|
throws(() => G[1][op](new Uint8Array([0])), 'ui8a([0])');
|
||||||
|
throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])');
|
||||||
|
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
|
||||||
|
if (G[1].toAffine) throws(() => G[1][op](C.Point.BASE), `Point ${op} ${pointName}`);
|
||||||
|
throws(() => G[1][op](o.BASE), `${op}/other curve point`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
should(`${name}/${pointName}/equals type check`, () => {
|
||||||
|
throws(() => G[1].equals(0), '0');
|
||||||
|
throws(() => G[1].equals(0n), '0n');
|
||||||
|
deepStrictEqual(G[1].equals(G[2]), false, '1*G != 2*G');
|
||||||
|
deepStrictEqual(G[1].equals(G[1]), true, '1*G == 1*G');
|
||||||
|
deepStrictEqual(G[2].equals(G[2]), true, '2*G == 2*G');
|
||||||
|
throws(() => G[1].equals(CURVE_ORDER), 'CURVE_ORDER');
|
||||||
|
throws(() => G[1].equals(123.456), '123.456');
|
||||||
|
throws(() => G[1].equals(true), 'true');
|
||||||
|
throws(() => G[1].equals('1'), "'1'");
|
||||||
|
throws(() => G[1].equals({ x: 1n, y: 1n, z: 1n, t: 1n }), '{ x: 1n, y: 1n, z: 1n, t: 1n }');
|
||||||
|
throws(() => G[1].equals(new Uint8Array([])), 'ui8a([])');
|
||||||
|
throws(() => G[1].equals(new Uint8Array([0])), 'ui8a([0])');
|
||||||
|
throws(() => G[1].equals(new Uint8Array([1])), 'ui8a([1])');
|
||||||
|
throws(() => G[1].equals(new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
|
||||||
|
if (G[1].toAffine) throws(() => G[1].equals(C.Point.BASE), `Point.equals(${pointName})`);
|
||||||
|
throws(() => G[1].equals(o.BASE), 'other curve point');
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const op of ['multiply', 'multiplyUnsafe']) {
|
||||||
|
if (!p.BASE[op]) continue;
|
||||||
|
should(`${name}/${pointName}/${op} type check`, () => {
|
||||||
|
if (op !== 'multiplyUnsafe') {
|
||||||
|
throws(() => G[1][op](0), '0');
|
||||||
|
throws(() => G[1][op](0n), '0n');
|
||||||
}
|
}
|
||||||
const verified = CURVE.verify(test.sig, m, pubKey);
|
G[1][op](1n);
|
||||||
deepStrictEqual(verified, true, 'valid');
|
G[1][op](CURVE_ORDER - 1n);
|
||||||
} else if (test.result === 'invalid') {
|
throws(() => G[1][op](G[2]), 'G[2]');
|
||||||
let failed = false;
|
throws(() => G[1][op](CURVE_ORDER), 'CURVE_ORDER');
|
||||||
try {
|
throws(() => G[1][op](CURVE_ORDER + 1n), 'CURVE_ORDER+1');
|
||||||
failed = !CURVE.verify(test.sig, m, pubKey);
|
throws(() => G[1][op](123.456), '123.456');
|
||||||
} catch (error) {
|
throws(() => G[1][op](true), 'true');
|
||||||
failed = true;
|
throws(() => G[1][op]('1'), '1');
|
||||||
}
|
throws(() => G[1][op](new Uint8Array([])), 'ui8a([])');
|
||||||
deepStrictEqual(failed, true, 'invalid');
|
throws(() => G[1][op](new Uint8Array([0])), 'ui8a([0])');
|
||||||
} else throw new Error('unknown test result');
|
throws(() => G[1][op](new Uint8Array([1])), 'ui8a([1])');
|
||||||
|
throws(() => G[1][op](new Uint8Array(4096).fill(1)), 'ui8a(4096*[1])');
|
||||||
|
throws(() => G[1][op](o.BASE), 'other curve point');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Complex point (Extended/Jacobian/Projective?)
|
||||||
|
if (p.BASE.toAffine) {
|
||||||
|
should(`${name}/${pointName}/toAffine()`, () => {
|
||||||
|
equal(p.ZERO.toAffine(), C.Point.ZERO, `0 = 0`);
|
||||||
|
equal(p.BASE.toAffine(), C.Point.BASE, `1 = 1`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (p.fromAffine) {
|
||||||
|
should(`${name}/${pointName}/fromAffine()`, () => {
|
||||||
|
equal(p.ZERO, p.fromAffine(C.Point.ZERO), `0 = 0`);
|
||||||
|
equal(p.BASE, p.fromAffine(C.Point.BASE), `1 = 1`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// toHex/fromHex (if available)
|
||||||
|
if (p.fromHex && p.BASE.toHex) {
|
||||||
|
should(`${name}/${pointName}/fromHex(toHex()) roundtrip`, () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(FC_BIGINT, (x) => {
|
||||||
|
const hex = p.BASE.multiply(x).toHex();
|
||||||
|
deepStrictEqual(p.fromHex(hex).toHex(), hex);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
// Generic complex things (getPublicKey/sign/verify/getSharedSecret)
|
||||||
|
should(`${name}/getPublicKey type check`, () => {
|
||||||
|
throws(() => C.getPublicKey(0), '0');
|
||||||
|
throws(() => C.getPublicKey(0n), '0n');
|
||||||
|
throws(() => C.getPublicKey(false), 'false');
|
||||||
|
throws(() => C.getPublicKey(123.456), '123.456');
|
||||||
|
throws(() => C.getPublicKey(true), 'true');
|
||||||
|
throws(() => C.getPublicKey(''), "''");
|
||||||
|
// NOTE: passes because of disabled hex padding checks for starknet, maybe enable?
|
||||||
|
//throws(() => C.getPublicKey('1'), "'1'");
|
||||||
|
throws(() => C.getPublicKey('key'), "'key'");
|
||||||
|
throws(() => C.getPublicKey(new Uint8Array([])));
|
||||||
|
throws(() => C.getPublicKey(new Uint8Array([0])));
|
||||||
|
throws(() => C.getPublicKey(new Uint8Array([1])));
|
||||||
|
throws(() => C.getPublicKey(new Uint8Array(4096).fill(1)));
|
||||||
|
});
|
||||||
|
should(`${name}.verify()/should verify random signatures`, () =>
|
||||||
|
fc.assert(
|
||||||
|
fc.property(fc.hexaString({ minLength: 64, maxLength: 64 }), (msg) => {
|
||||||
|
const priv = C.utils.randomPrivateKey();
|
||||||
|
const pub = C.getPublicKey(priv);
|
||||||
|
const sig = C.sign(msg, priv);
|
||||||
|
deepStrictEqual(C.verify(sig, msg, pub), true);
|
||||||
|
}),
|
||||||
|
{ numRuns: NUM_RUNS }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
should(`${name}.sign()/edge cases`, () => {
|
||||||
|
throws(() => C.sign());
|
||||||
|
throws(() => C.sign(''));
|
||||||
|
});
|
||||||
|
|
||||||
should('wychenproof ECDH vectors', () => {
|
should(`${name}.verify()/should not verify signature with wrong hash`, () => {
|
||||||
for (const group of ecdh.testGroups) {
|
const MSG = '01'.repeat(32);
|
||||||
// // Tested in secp256k1.test.js
|
const PRIV_KEY = 0x2n;
|
||||||
// if (group.key.curve === 'secp256k1') continue;
|
const WRONG_MSG = '11'.repeat(32);
|
||||||
// We don't have SHA-224
|
const signature = C.sign(MSG, PRIV_KEY);
|
||||||
const CURVE = nist[group.curve];
|
const publicKey = C.getPublicKey(PRIV_KEY);
|
||||||
if (!CURVE) continue;
|
deepStrictEqual(C.verify(signature, WRONG_MSG, publicKey), false);
|
||||||
for (const test of group.tests) {
|
});
|
||||||
if (test.result === 'valid' || test.result === 'acceptable') {
|
// NOTE: fails for ed, because of empty message. Since we convert it to scalar,
|
||||||
|
// need to check what other implementations do. Empty message != new Uint8Array([0]), but what scalar should be in that case?
|
||||||
|
// should(`${name}/should not verify signature with wrong message`, () => {
|
||||||
|
// fc.assert(
|
||||||
|
// fc.property(
|
||||||
|
// fc.array(fc.integer({ min: 0x00, max: 0xff })),
|
||||||
|
// fc.array(fc.integer({ min: 0x00, max: 0xff })),
|
||||||
|
// (bytes, wrongBytes) => {
|
||||||
|
// const privKey = C.utils.randomPrivateKey();
|
||||||
|
// const message = new Uint8Array(bytes);
|
||||||
|
// const wrongMessage = new Uint8Array(wrongBytes);
|
||||||
|
// const publicKey = C.getPublicKey(privKey);
|
||||||
|
// const signature = C.sign(message, privKey);
|
||||||
|
// deepStrictEqual(
|
||||||
|
// C.verify(signature, wrongMessage, publicKey),
|
||||||
|
// bytes.toString() === wrongBytes.toString()
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// ),
|
||||||
|
// { numRuns: NUM_RUNS }
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
if (C.getSharedSecret) {
|
||||||
|
should(`${name}/getSharedSecret() should be commutative`, () => {
|
||||||
|
for (let i = 0; i < NUM_RUNS; i++) {
|
||||||
|
const asec = C.utils.randomPrivateKey();
|
||||||
|
const apub = C.getPublicKey(asec);
|
||||||
|
const bsec = C.utils.randomPrivateKey();
|
||||||
|
const bpub = C.getPublicKey(bsec);
|
||||||
try {
|
try {
|
||||||
const pub = CURVE.Point.fromHex(test.public);
|
deepStrictEqual(C.getSharedSecret(asec, bpub), C.getSharedSecret(bsec, apub));
|
||||||
} catch (e) {
|
|
||||||
if (e.message.includes('Point.fromHex: received invalid point.')) continue;
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
const shared = CURVE.getSharedSecret(test.private, test.public);
|
|
||||||
deepStrictEqual(shared, test.shared, 'valid');
|
|
||||||
} else if (test.result === 'invalid') {
|
|
||||||
let failed = false;
|
|
||||||
try {
|
|
||||||
CURVE.getSharedSecret(test.private, test.public);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
failed = true;
|
console.error('not commutative', { asec, apub, bsec, bpub });
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
deepStrictEqual(failed, true, 'invalid');
|
}
|
||||||
} else throw new Error('unknown test result');
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// ESM is broken.
|
// ESM is broken.
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
|||||||
657
curve-definitions/test/ed25519.test.js
Normal file
657
curve-definitions/test/ed25519.test.js
Normal file
@@ -0,0 +1,657 @@
|
|||||||
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
|
import { should } from 'micro-should';
|
||||||
|
import * as fc from 'fast-check';
|
||||||
|
import { ed25519, ed25519ctx, ed25519ph, x25519, RistrettoPoint } from '../lib/ed25519.js';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { default as zip215 } from './ed25519/zip215.json' assert { type: 'json' };
|
||||||
|
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
|
||||||
|
import { numberToBytesLE } from '@noble/curves/utils';
|
||||||
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
|
import { default as ed25519vectors } from './wycheproof/eddsa_test.json' assert { type: 'json' };
|
||||||
|
import { default as x25519vectors } from './wycheproof/x25519_test.json' assert { type: 'json' };
|
||||||
|
|
||||||
|
const ed = ed25519;
|
||||||
|
const hex = bytesToHex;
|
||||||
|
|
||||||
|
function to32Bytes(numOrStr) {
|
||||||
|
let hex = typeof numOrStr === 'string' ? numOrStr : numOrStr.toString(16);
|
||||||
|
return hexToBytes(hex.padStart(64, '0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function utf8ToBytes(str) {
|
||||||
|
if (typeof str !== 'string') {
|
||||||
|
throw new TypeError(`utf8ToBytes expected string, got ${typeof str}`);
|
||||||
|
}
|
||||||
|
return new TextEncoder().encode(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
ed.utils.precompute(8);
|
||||||
|
|
||||||
|
should('ed25519/should not accept >32byte private keys', () => {
|
||||||
|
const invalidPriv =
|
||||||
|
100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n;
|
||||||
|
throws(() => ed.getPublicKey(invalidPriv));
|
||||||
|
});
|
||||||
|
should('ed25519/should verify recent signature', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(
|
||||||
|
fc.hexaString({ minLength: 2, maxLength: 32 }),
|
||||||
|
fc.bigInt(2n, ed.CURVE.n),
|
||||||
|
(message, privateKey) => {
|
||||||
|
const publicKey = ed.getPublicKey(to32Bytes(privateKey));
|
||||||
|
const signature = ed.sign(to32Bytes(message), to32Bytes(privateKey));
|
||||||
|
deepStrictEqual(publicKey.length, 32);
|
||||||
|
deepStrictEqual(signature.length, 64);
|
||||||
|
deepStrictEqual(ed.verify(signature, to32Bytes(message), publicKey), true);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{ numRuns: 5 }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
should('ed25519/should not verify signature with wrong message', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(
|
||||||
|
fc.array(fc.integer({ min: 0x00, max: 0xff })),
|
||||||
|
fc.array(fc.integer({ min: 0x00, max: 0xff })),
|
||||||
|
fc.bigInt(1n, ed.CURVE.n),
|
||||||
|
(bytes, wrongBytes, privateKey) => {
|
||||||
|
const privKey = to32Bytes(privateKey);
|
||||||
|
const message = new Uint8Array(bytes);
|
||||||
|
const wrongMessage = new Uint8Array(wrongBytes);
|
||||||
|
const publicKey = ed.getPublicKey(privKey);
|
||||||
|
const signature = ed.sign(message, privKey);
|
||||||
|
deepStrictEqual(
|
||||||
|
ed.verify(signature, wrongMessage, publicKey),
|
||||||
|
bytes.toString() === wrongBytes.toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{ numRuns: 5 }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const privKey = to32Bytes('a665a45920422f9d417e4867ef');
|
||||||
|
const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a');
|
||||||
|
const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c');
|
||||||
|
should('ed25519/basic methods/should sign and verify', () => {
|
||||||
|
const publicKey = ed.getPublicKey(privKey);
|
||||||
|
const signature = ed.sign(msg, privKey);
|
||||||
|
deepStrictEqual(ed.verify(signature, msg, publicKey), true);
|
||||||
|
});
|
||||||
|
should('ed25519/basic methods/should not verify signature with wrong public key', () => {
|
||||||
|
const publicKey = ed.getPublicKey(12);
|
||||||
|
const signature = ed.sign(msg, privKey);
|
||||||
|
deepStrictEqual(ed.verify(signature, msg, publicKey), false);
|
||||||
|
});
|
||||||
|
should('ed25519/basic methods/should not verify signature with wrong hash', () => {
|
||||||
|
const publicKey = ed.getPublicKey(privKey);
|
||||||
|
const signature = ed.sign(msg, privKey);
|
||||||
|
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('ed25519/sync methods/should sign and verify', () => {
|
||||||
|
const publicKey = ed.getPublicKey(privKey);
|
||||||
|
const signature = ed.sign(msg, privKey);
|
||||||
|
deepStrictEqual(ed.verify(signature, msg, publicKey), true);
|
||||||
|
});
|
||||||
|
should('ed25519/sync methods/should not verify signature with wrong public key', () => {
|
||||||
|
const publicKey = ed.getPublicKey(12);
|
||||||
|
const signature = ed.sign(msg, privKey);
|
||||||
|
deepStrictEqual(ed.verify(signature, msg, publicKey), false);
|
||||||
|
});
|
||||||
|
should('ed25519/sync methods/should not verify signature with wrong hash', () => {
|
||||||
|
const publicKey = ed.getPublicKey(privKey);
|
||||||
|
const signature = ed.sign(msg, privKey);
|
||||||
|
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// https://xmr.llcoins.net/addresstests.html
|
||||||
|
should(
|
||||||
|
'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 1',
|
||||||
|
() => {
|
||||||
|
const publicKey =
|
||||||
|
ed.Point.BASE.multiply(0x90af56259a4b6bfbc4337980d5d75fbe3c074630368ff3804d33028e5dbfa77n);
|
||||||
|
deepStrictEqual(
|
||||||
|
publicKey.toHex(),
|
||||||
|
'0f3b913371411b27e646b537e888f685bf929ea7aab93c950ed84433f064480d'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
should(
|
||||||
|
'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 2',
|
||||||
|
() => {
|
||||||
|
const publicKey =
|
||||||
|
ed.Point.BASE.multiply(0x364e8711a60780382a5d57b061c126f039940f28a9e91fe039d4d3094d8b88n);
|
||||||
|
deepStrictEqual(
|
||||||
|
publicKey.toHex(),
|
||||||
|
'ad545340b58610f0cd62f17d55af1ab11ecde9c084d5476865ddb4dbda015349'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
should(
|
||||||
|
'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 3',
|
||||||
|
() => {
|
||||||
|
const publicKey =
|
||||||
|
ed.Point.BASE.multiply(0xb9bf90ff3abec042752cac3a07a62f0c16cfb9d32a3fc2305d676ec2d86e941n);
|
||||||
|
deepStrictEqual(
|
||||||
|
publicKey.toHex(),
|
||||||
|
'e097c4415fe85724d522b2e449e8fd78dd40d20097bdc9ae36fe8ec6fe12cb8c'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
should(
|
||||||
|
'ed25519/BASE_POINT.multiply()/should create right publicKey without SHA-512 hashing TEST 4',
|
||||||
|
() => {
|
||||||
|
const publicKey =
|
||||||
|
ed.Point.BASE.multiply(0x69d896f02d79524c9878e080308180e2859d07f9f54454e0800e8db0847a46en);
|
||||||
|
deepStrictEqual(
|
||||||
|
publicKey.toHex(),
|
||||||
|
'f12cb7c43b59971395926f278ce7c2eaded9444fbce62ca717564cb508a0db1d'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
should('ed25519/BASE_POINT.multiply()/should throw Point#multiply on TEST 5', () => {
|
||||||
|
for (const num of [0n, 0, -1n, -1, 1.1]) {
|
||||||
|
throws(() => ed.Point.BASE.multiply(num));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// https://ed25519.cr.yp.to/python/sign.py
|
||||||
|
// https://ed25519.cr.yp.to/python/sign.input
|
||||||
|
const data = readFileSync('./test/ed25519/vectors.txt', 'utf-8');
|
||||||
|
const vectors = data
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => line.split(':'));
|
||||||
|
should('ed25519 official vectors/should match 1024 official vectors', () => {
|
||||||
|
for (let i = 0; i < vectors.length; i++) {
|
||||||
|
const vector = vectors[i];
|
||||||
|
// Extract.
|
||||||
|
const priv = vector[0].slice(0, 64);
|
||||||
|
const expectedPub = vector[1];
|
||||||
|
const msg = vector[2];
|
||||||
|
const expectedSignature = vector[3].slice(0, 128);
|
||||||
|
|
||||||
|
// Calculate
|
||||||
|
const pub = ed.getPublicKey(to32Bytes(priv));
|
||||||
|
deepStrictEqual(hex(pub), expectedPub);
|
||||||
|
deepStrictEqual(pub, ed.Point.fromHex(pub).toRawBytes());
|
||||||
|
|
||||||
|
const signature = hex(ed.sign(msg, priv));
|
||||||
|
// console.log('vector', i);
|
||||||
|
// expect(pub).toBe(expectedPub);
|
||||||
|
deepStrictEqual(signature, expectedSignature);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc8032#section-7
|
||||||
|
should('rfc8032 vectors/should create right signature for 0x9d and empty string', () => {
|
||||||
|
const privateKey = '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60';
|
||||||
|
const publicKey = ed.getPublicKey(privateKey);
|
||||||
|
const message = '';
|
||||||
|
const signature = ed.sign(message, privateKey);
|
||||||
|
deepStrictEqual(
|
||||||
|
hex(publicKey),
|
||||||
|
'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
hex(signature),
|
||||||
|
'e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
should('rfc8032 vectors/should create right signature for 0x4c and 72', () => {
|
||||||
|
const privateKey = '4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb';
|
||||||
|
const publicKey = ed.getPublicKey(privateKey);
|
||||||
|
const message = '72';
|
||||||
|
const signature = ed.sign(message, privateKey);
|
||||||
|
deepStrictEqual(
|
||||||
|
hex(publicKey),
|
||||||
|
'3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
hex(signature),
|
||||||
|
'92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
should('rfc8032 vectors/should create right signature for 0x00 and 5a', () => {
|
||||||
|
const privateKey = '002fdd1f7641793ab064bb7aa848f762e7ec6e332ffc26eeacda141ae33b1783';
|
||||||
|
const publicKey = ed.getPublicKey(privateKey);
|
||||||
|
const message =
|
||||||
|
'5ac1dfc324f43e6cb79a87ab0470fa857b51fb944982e19074ca44b1e40082c1d07b92efa7ea55ad42b7c027e0b9e33756d95a2c1796a7c2066811dc41858377d4b835c1688d638884cd2ad8970b74c1a54aadd27064163928a77988b24403aa85af82ceab6b728e554761af7175aeb99215b7421e4474c04d213e01ff03e3529b11077cdf28964b8c49c5649e3a46fa0a09dcd59dcad58b9b922a83210acd5e65065531400234f5e40cddcf9804968e3e9ac6f5c44af65001e158067fc3a660502d13fa8874fa93332138d9606bc41b4cee7edc39d753dae12a873941bb357f7e92a4498847d6605456cb8c0b425a47d7d3ca37e54e903a41e6450a35ebe5237c6f0c1bbbc1fd71fb7cd893d189850295c199b7d88af26bc8548975fda1099ffefee42a52f3428ddff35e0173d3339562507ac5d2c45bbd2c19cfe89b';
|
||||||
|
const signature = ed.sign(message, privateKey);
|
||||||
|
deepStrictEqual(
|
||||||
|
hex(publicKey),
|
||||||
|
'77d1d8ebacd13f4e2f8a40e28c4a63bc9ce3bfb69716334bcb28a33eb134086c'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
hex(signature),
|
||||||
|
'0df3aa0d0999ad3dc580378f52d152700d5b3b057f56a66f92112e441e1cb9123c66f18712c87efe22d2573777296241216904d7cdd7d5ea433928bd2872fa0c'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
should('rfc8032 vectors/should create right signature for 0xf5 and long msg', () => {
|
||||||
|
const privateKey = 'f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5';
|
||||||
|
const publicKey = ed.getPublicKey(privateKey);
|
||||||
|
const message =
|
||||||
|
'08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d879de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4feba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbefefd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed185ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2d17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f27088d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b0707e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128bab27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51addd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429ec96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb751fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34dff7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08d78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e488acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a32ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5fb93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b50d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380db2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0';
|
||||||
|
const signature = ed.sign(message, privateKey);
|
||||||
|
deepStrictEqual(
|
||||||
|
hex(publicKey),
|
||||||
|
'278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e'
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
hex(signature),
|
||||||
|
'0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// const PRIVATE_KEY = 0xa665a45920422f9d417e4867efn;
|
||||||
|
// const MESSAGE = ripemd160(new Uint8Array([97, 98, 99, 100, 101, 102, 103]));
|
||||||
|
// prettier-ignore
|
||||||
|
// const MESSAGE = new Uint8Array([
|
||||||
|
// 135, 79, 153, 96, 197, 210, 183, 169, 181, 250, 211, 131, 225, 186, 68, 113, 158, 187, 116, 58,
|
||||||
|
// ]);
|
||||||
|
// const WRONG_MESSAGE = ripemd160(new Uint8Array([98, 99, 100, 101, 102, 103]));
|
||||||
|
// prettier-ignore
|
||||||
|
// const WRONG_MESSAGE = new Uint8Array([
|
||||||
|
// 88, 157, 140, 127, 29, 160, 162, 75, 192, 123, 115, 129, 173, 72, 177, 207, 194, 17, 175, 28,
|
||||||
|
// ]);
|
||||||
|
// // it("should verify just signed message", async () => {
|
||||||
|
// // await fc.assert(fc.asyncProperty(
|
||||||
|
// // fc.hexa(),
|
||||||
|
// // fc.bigInt(2n, ristretto25519.PRIME_ORDER),
|
||||||
|
// // async (message, privateKey) => {
|
||||||
|
// // const publicKey = await ristretto25519.getPublicKey(privateKey);
|
||||||
|
// // const signature = await ristretto25519.sign(message, privateKey);
|
||||||
|
// // expect(publicKey.length).toBe(32);
|
||||||
|
// // expect(signature.length).toBe(64);
|
||||||
|
// // expect(await ristretto25519.verify(signature, message, publicKey)).toBe(true);
|
||||||
|
// // }),
|
||||||
|
// // { numRuns: 1 }
|
||||||
|
// // );
|
||||||
|
// // });
|
||||||
|
// // it("should not verify sign with wrong message", async () => {
|
||||||
|
// // await fc.assert(fc.asyncProperty(
|
||||||
|
// // fc.array(fc.integer(0x00, 0xff)),
|
||||||
|
// // fc.array(fc.integer(0x00, 0xff)),
|
||||||
|
// // fc.bigInt(2n, ristretto25519.PRIME_ORDER),
|
||||||
|
// // async (bytes, wrongBytes, privateKey) => {
|
||||||
|
// // const message = new Uint8Array(bytes);
|
||||||
|
// // const wrongMessage = new Uint8Array(wrongBytes);
|
||||||
|
// // const publicKey = await ristretto25519.getPublicKey(privateKey);
|
||||||
|
// // const signature = await ristretto25519.sign(message, privateKey);
|
||||||
|
// // expect(await ristretto25519.verify(signature, wrongMessage, publicKey)).toBe(
|
||||||
|
// // bytes.toString() === wrongBytes.toString()
|
||||||
|
// // );
|
||||||
|
// // }),
|
||||||
|
// // { numRuns: 1 }
|
||||||
|
// // );
|
||||||
|
// // });
|
||||||
|
// // it("should sign and verify", async () => {
|
||||||
|
// // const publicKey = await ristretto25519.getPublicKey(PRIVATE_KEY);
|
||||||
|
// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY);
|
||||||
|
// // expect(await ristretto25519.verify(signature, MESSAGE, publicKey)).toBe(true);
|
||||||
|
// // });
|
||||||
|
// // it("should not verify signature with wrong public key", async () => {
|
||||||
|
// // const publicKey = await ristretto25519.getPublicKey(12);
|
||||||
|
// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY);
|
||||||
|
// // expect(await ristretto25519.verify(signature, MESSAGE, publicKey)).toBe(false);
|
||||||
|
// // });
|
||||||
|
// // it("should not verify signature with wrong hash", async () => {
|
||||||
|
// // const publicKey = await ristretto25519.getPublicKey(PRIVATE_KEY);
|
||||||
|
// // const signature = await ristretto25519.sign(MESSAGE, PRIVATE_KEY);
|
||||||
|
// // expect(await ristretto25519.verify(signature, WRONG_MESSAGE, publicKey)).toBe(false);
|
||||||
|
// // });
|
||||||
|
should('ristretto255/should follow the byte encodings of small multiples', () => {
|
||||||
|
const encodingsOfSmallMultiples = [
|
||||||
|
// This is the identity point
|
||||||
|
'0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
// This is the basepoint
|
||||||
|
'e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76',
|
||||||
|
// These are small multiples of the basepoint
|
||||||
|
'6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919',
|
||||||
|
'94741f5d5d52755ece4f23f044ee27d5d1ea1e2bd196b462166b16152a9d0259',
|
||||||
|
'da80862773358b466ffadfe0b3293ab3d9fd53c5ea6c955358f568322daf6a57',
|
||||||
|
'e882b131016b52c1d3337080187cf768423efccbb517bb495ab812c4160ff44e',
|
||||||
|
'f64746d3c92b13050ed8d80236a7f0007c3b3f962f5ba793d19a601ebb1df403',
|
||||||
|
'44f53520926ec81fbd5a387845beb7df85a96a24ece18738bdcfa6a7822a176d',
|
||||||
|
'903293d8f2287ebe10e2374dc1a53e0bc887e592699f02d077d5263cdd55601c',
|
||||||
|
'02622ace8f7303a31cafc63f8fc48fdc16e1c8c8d234b2f0d6685282a9076031',
|
||||||
|
'20706fd788b2720a1ed2a5dad4952b01f413bcf0e7564de8cdc816689e2db95f',
|
||||||
|
'bce83f8ba5dd2fa572864c24ba1810f9522bc6004afe95877ac73241cafdab42',
|
||||||
|
'e4549ee16b9aa03099ca208c67adafcafa4c3f3e4e5303de6026e3ca8ff84460',
|
||||||
|
'aa52e000df2e16f55fb1032fc33bc42742dad6bd5a8fc0be0167436c5948501f',
|
||||||
|
'46376b80f409b29dc2b5f6f0c52591990896e5716f41477cd30085ab7f10301e',
|
||||||
|
'e0c418f7c8d9c4cdd7395b93ea124f3ad99021bb681dfc3302a9d99a2e53e64e',
|
||||||
|
];
|
||||||
|
let B = RistrettoPoint.BASE;
|
||||||
|
let P = RistrettoPoint.ZERO;
|
||||||
|
for (const encoded of encodingsOfSmallMultiples) {
|
||||||
|
deepStrictEqual(P.toHex(), encoded);
|
||||||
|
deepStrictEqual(RistrettoPoint.fromHex(encoded).toHex(), encoded);
|
||||||
|
P = P.add(B);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('ristretto255/should not convert bad bytes encoding', () => {
|
||||||
|
const badEncodings = [
|
||||||
|
// These are all bad because they're non-canonical field encodings.
|
||||||
|
'00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||||
|
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
|
'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
|
'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
|
// These are all bad because they're negative field elements.
|
||||||
|
'0100000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
'01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
|
'ed57ffd8c914fb201471d1c3d245ce3c746fcbe63a3679d51b6a516ebebe0e20',
|
||||||
|
'c34c4e1826e5d403b78e246e88aa051c36ccf0aafebffe137d148a2bf9104562',
|
||||||
|
'c940e5a4404157cfb1628b108db051a8d439e1a421394ec4ebccb9ec92a8ac78',
|
||||||
|
'47cfc5497c53dc8e61c91d17fd626ffb1c49e2bca94eed052281b510b1117a24',
|
||||||
|
'f1c6165d33367351b0da8f6e4511010c68174a03b6581212c71c0e1d026c3c72',
|
||||||
|
'87260f7a2f12495118360f02c26a470f450dadf34a413d21042b43b9d93e1309',
|
||||||
|
// These are all bad because they give a nonsquare x^2.
|
||||||
|
'26948d35ca62e643e26a83177332e6b6afeb9d08e4268b650f1f5bbd8d81d371',
|
||||||
|
'4eac077a713c57b4f4397629a4145982c661f48044dd3f96427d40b147d9742f',
|
||||||
|
'de6a7b00deadc788eb6b6c8d20c0ae96c2f2019078fa604fee5b87d6e989ad7b',
|
||||||
|
'bcab477be20861e01e4a0e295284146a510150d9817763caf1a6f4b422d67042',
|
||||||
|
'2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08',
|
||||||
|
'f4a9e534fc0d216c44b218fa0c42d99635a0127ee2e53c712f70609649fdff22',
|
||||||
|
'8268436f8c4126196cf64b3c7ddbda90746a378625f9813dd9b8457077256731',
|
||||||
|
'2810e5cbc2cc4d4eece54f61c6f69758e289aa7ab440b3cbeaa21995c2f4232b',
|
||||||
|
// These are all bad because they give a negative xy value.
|
||||||
|
'3eb858e78f5a7254d8c9731174a94f76755fd3941c0ac93735c07ba14579630e',
|
||||||
|
'a45fdc55c76448c049a1ab33f17023edfb2be3581e9c7aade8a6125215e04220',
|
||||||
|
'd483fe813c6ba647ebbfd3ec41adca1c6130c2beeee9d9bf065c8d151c5f396e',
|
||||||
|
'8a2e1d30050198c65a54483123960ccc38aef6848e1ec8f5f780e8523769ba32',
|
||||||
|
'32888462f8b486c68ad7dd9610be5192bbeaf3b443951ac1a8118419d9fa097b',
|
||||||
|
'227142501b9d4355ccba290404bde41575b037693cef1f438c47f8fbf35d1165',
|
||||||
|
'5c37cc491da847cfeb9281d407efc41e15144c876e0170b499a96a22ed31e01e',
|
||||||
|
'445425117cb8c90edcbc7c1cc0e74f747f2c1efa5630a967c64f287792a48a4b',
|
||||||
|
// This is s = -1, which causes y = 0.
|
||||||
|
'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f',
|
||||||
|
];
|
||||||
|
for (const badBytes of badEncodings) {
|
||||||
|
const b = hexToBytes(badBytes);
|
||||||
|
throws(() => RistrettoPoint.fromHex(b), badBytes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('ristretto255/should create right points from uniform hash', async () => {
|
||||||
|
const labels = [
|
||||||
|
'Ristretto is traditionally a short shot of espresso coffee',
|
||||||
|
'made with the normal amount of ground coffee but extracted with',
|
||||||
|
'about half the amount of water in the same amount of time',
|
||||||
|
'by using a finer grind.',
|
||||||
|
'This produces a concentrated shot of coffee per volume.',
|
||||||
|
'Just pulling a normal shot short will produce a weaker shot',
|
||||||
|
'and is not a Ristretto as some believe.',
|
||||||
|
];
|
||||||
|
const encodedHashToPoints = [
|
||||||
|
'3066f82a1a747d45120d1740f14358531a8f04bbffe6a819f86dfe50f44a0a46',
|
||||||
|
'f26e5b6f7d362d2d2a94c5d0e7602cb4773c95a2e5c31a64f133189fa76ed61b',
|
||||||
|
'006ccd2a9e6867e6a2c5cea83d3302cc9de128dd2a9a57dd8ee7b9d7ffe02826',
|
||||||
|
'f8f0c87cf237953c5890aec3998169005dae3eca1fbb04548c635953c817f92a',
|
||||||
|
'ae81e7dedf20a497e10c304a765c1767a42d6e06029758d2d7e8ef7cc4c41179',
|
||||||
|
'e2705652ff9f5e44d3e841bf1c251cf7dddb77d140870d1ab2ed64f1a9ce8628',
|
||||||
|
'80bd07262511cdde4863f8a7434cef696750681cb9510eea557088f76d9e5065',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < labels.length; i++) {
|
||||||
|
const hash = sha512(utf8ToBytes(labels[i]));
|
||||||
|
const point = RistrettoPoint.hashToCurve(hash);
|
||||||
|
deepStrictEqual(point.toHex(), encodedHashToPoints[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('input immutability: sign/verify are immutable', () => {
|
||||||
|
const privateKey = ed.utils.randomPrivateKey();
|
||||||
|
const publicKey = ed.getPublicKey(privateKey);
|
||||||
|
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
let payload = randomBytes(100);
|
||||||
|
let signature = ed.sign(payload, privateKey);
|
||||||
|
if (!ed.verify(signature, payload, publicKey)) {
|
||||||
|
throw new Error('Signature verification failed');
|
||||||
|
}
|
||||||
|
const signatureCopy = Buffer.alloc(signature.byteLength);
|
||||||
|
signatureCopy.set(signature, 0); // <-- breaks
|
||||||
|
payload = payload.slice();
|
||||||
|
signature = signature.slice();
|
||||||
|
|
||||||
|
if (!ed.verify(signatureCopy, payload, publicKey))
|
||||||
|
throw new Error('Copied signature verification failed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// https://zips.z.cash/zip-0215
|
||||||
|
// Vectors from https://gist.github.com/hdevalence/93ed42d17ecab8e42138b213812c8cc7
|
||||||
|
should('ZIP-215 compliance tests/should pass all of them', () => {
|
||||||
|
const str = utf8ToBytes('Zcash');
|
||||||
|
for (let v of zip215) {
|
||||||
|
let noble = false;
|
||||||
|
try {
|
||||||
|
noble = ed.verify(v.sig_bytes, str, v.vk_bytes);
|
||||||
|
} catch (e) {
|
||||||
|
noble = false;
|
||||||
|
}
|
||||||
|
deepStrictEqual(noble, v.valid_zip215);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
should('ZIP-215 compliance tests/disallows sig.s >= CURVE.n', () => {
|
||||||
|
const sig = new ed.Signature(ed.Point.BASE, 1n);
|
||||||
|
sig.s = ed.CURVE.n + 1n;
|
||||||
|
throws(() => ed.verify(sig, 'deadbeef', ed.Point.BASE));
|
||||||
|
});
|
||||||
|
|
||||||
|
const rfc7748Mul = [
|
||||||
|
{
|
||||||
|
scalar: 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4',
|
||||||
|
u: 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c',
|
||||||
|
outputU: 'c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scalar: '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d',
|
||||||
|
u: 'e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493',
|
||||||
|
outputU: '95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for (let i = 0; i < rfc7748Mul.length; i++) {
|
||||||
|
const v = rfc7748Mul[i];
|
||||||
|
should(`RFC7748: scalarMult (${i})`, () => {
|
||||||
|
deepStrictEqual(hex(x25519.scalarMult(v.u, v.scalar)), v.outputU);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const rfc7748Iter = [
|
||||||
|
{ scalar: '422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079', iters: 1 },
|
||||||
|
{ scalar: '684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51', iters: 1000 },
|
||||||
|
// { scalar: '7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424', iters: 1000000 },
|
||||||
|
];
|
||||||
|
for (let i = 0; i < rfc7748Iter.length; i++) {
|
||||||
|
const { scalar, iters } = rfc7748Iter[i];
|
||||||
|
should(`RFC7748: scalarMult iteration (${i})`, () => {
|
||||||
|
let k = x25519.Gu;
|
||||||
|
for (let i = 0, u = k; i < iters; i++) [k, u] = [x25519.scalarMult(u, k), k];
|
||||||
|
deepStrictEqual(hex(k), scalar);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
should('RFC7748 getSharedKey', () => {
|
||||||
|
const alicePrivate = '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a';
|
||||||
|
const alicePublic = '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a';
|
||||||
|
const bobPrivate = '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb';
|
||||||
|
const bobPublic = 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f';
|
||||||
|
const shared = '4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742';
|
||||||
|
deepStrictEqual(alicePublic, hex(x25519.getPublicKey(alicePrivate)));
|
||||||
|
deepStrictEqual(bobPublic, hex(x25519.getPublicKey(bobPrivate)));
|
||||||
|
deepStrictEqual(hex(x25519.scalarMult(bobPublic, alicePrivate)), shared);
|
||||||
|
deepStrictEqual(hex(x25519.scalarMult(alicePublic, bobPrivate)), shared);
|
||||||
|
});
|
||||||
|
|
||||||
|
// should('X25519/getSharedSecret() should be commutative', () => {
|
||||||
|
// for (let i = 0; i < 512; i++) {
|
||||||
|
// const asec = ed.utils.randomPrivateKey();
|
||||||
|
// const apub = ed.getPublicKey(asec);
|
||||||
|
// const bsec = ed.utils.randomPrivateKey();
|
||||||
|
// const bpub = ed.getPublicKey(bsec);
|
||||||
|
// try {
|
||||||
|
// deepStrictEqual(ed.getSharedSecret(asec, bpub), ed.getSharedSecret(bsec, apub));
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('not commutative', { asec, apub, bsec, bpub });
|
||||||
|
// throw error;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// should('X25519: should convert base point to montgomery using fromPoint', () => {
|
||||||
|
// deepStrictEqual(
|
||||||
|
// hex(ed.montgomeryCurve.UfromPoint(ed.Point.BASE)),
|
||||||
|
// ed.montgomeryCurve.BASE_POINT_U
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
{
|
||||||
|
const group = x25519vectors.testGroups[0];
|
||||||
|
should(`Wycheproof/X25519`, () => {
|
||||||
|
for (let i = 0; i < group.tests.length; i++) {
|
||||||
|
const v = group.tests[i];
|
||||||
|
const comment = `(${i}, ${v.result}) ${v.comment}`;
|
||||||
|
if (v.result === 'valid' || v.result === 'acceptable') {
|
||||||
|
try {
|
||||||
|
const shared = hex(x25519.scalarMult(v.public, v.private));
|
||||||
|
deepStrictEqual(shared, v.shared, comment);
|
||||||
|
} catch (e) {
|
||||||
|
// We are more strict
|
||||||
|
if (e.message.includes('Expected valid scalar')) return;
|
||||||
|
if (e.message.includes('Invalid private or public key received')) return;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else if (v.result === 'invalid') {
|
||||||
|
let failed = false;
|
||||||
|
try {
|
||||||
|
x25519.scalarMult(v.public, v.private);
|
||||||
|
} catch (error) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
deepStrictEqual(failed, true, comment);
|
||||||
|
} else throw new Error('unknown test result');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
should(`Wycheproof/ED25519`, () => {
|
||||||
|
for (let g = 0; g < ed25519vectors.testGroups.length; g++) {
|
||||||
|
const group = ed25519vectors.testGroups[g];
|
||||||
|
const key = group.key;
|
||||||
|
deepStrictEqual(hex(ed.getPublicKey(key.sk)), key.pk, `(${g}, public)`);
|
||||||
|
for (let i = 0; i < group.tests.length; i++) {
|
||||||
|
const v = group.tests[i];
|
||||||
|
const comment = `(${g}/${i}, ${v.result}): ${v.comment}`;
|
||||||
|
if (v.result === 'valid' || v.result === 'acceptable') {
|
||||||
|
deepStrictEqual(hex(ed.sign(v.msg, key.sk)), v.sig, comment);
|
||||||
|
deepStrictEqual(ed.verify(v.sig, v.msg, key.pk), true, comment);
|
||||||
|
} else if (v.result === 'invalid') {
|
||||||
|
let failed = false;
|
||||||
|
try {
|
||||||
|
failed = !ed.verify(v.sig, v.msg, key.pk);
|
||||||
|
} catch (error) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
deepStrictEqual(failed, true, comment);
|
||||||
|
} else throw new Error('unknown test result');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Property test issue #1', () => {
|
||||||
|
const message = new Uint8Array([12, 12, 12]);
|
||||||
|
const signature = ed.sign(message, to32Bytes(1n));
|
||||||
|
const publicKey = ed.getPublicKey(to32Bytes(1n)); // <- was 1n
|
||||||
|
deepStrictEqual(ed.verify(signature, message, publicKey), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
const VECTORS_RFC8032_CTX = [
|
||||||
|
{
|
||||||
|
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
|
||||||
|
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
|
||||||
|
message: 'f726936d19c800494e3fdaff20b276a8',
|
||||||
|
context: '666f6f',
|
||||||
|
signature:
|
||||||
|
'55a4cc2f70a54e04288c5f4cd1e45a7b' +
|
||||||
|
'b520b36292911876cada7323198dd87a' +
|
||||||
|
'8b36950b95130022907a7fb7c4e9b2d5' +
|
||||||
|
'f6cca685a587b4b21f4b888e4e7edb0d',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
|
||||||
|
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
|
||||||
|
message: 'f726936d19c800494e3fdaff20b276a8',
|
||||||
|
context: '626172',
|
||||||
|
signature:
|
||||||
|
'fc60d5872fc46b3aa69f8b5b4351d580' +
|
||||||
|
'8f92bcc044606db097abab6dbcb1aee3' +
|
||||||
|
'216c48e8b3b66431b5b186d1d28f8ee1' +
|
||||||
|
'5a5ca2df6668346291c2043d4eb3e90d',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secretKey: '0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6',
|
||||||
|
publicKey: 'dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292',
|
||||||
|
message: '508e9e6882b979fea900f62adceaca35',
|
||||||
|
context: '666f6f',
|
||||||
|
signature:
|
||||||
|
'8b70c1cc8310e1de20ac53ce28ae6e72' +
|
||||||
|
'07f33c3295e03bb5c0732a1d20dc6490' +
|
||||||
|
'8922a8b052cf99b7c4fe107a5abb5b2c' +
|
||||||
|
'4085ae75890d02df26269d8945f84b0b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secretKey: 'ab9c2853ce297ddab85c993b3ae14bcad39b2c682beabc27d6d4eb20711d6560',
|
||||||
|
publicKey: '0f1d1274943b91415889152e893d80e93275a1fc0b65fd71b4b0dda10ad7d772',
|
||||||
|
message: 'f726936d19c800494e3fdaff20b276a8',
|
||||||
|
context: '666f6f',
|
||||||
|
signature:
|
||||||
|
'21655b5f1aa965996b3f97b3c849eafb' +
|
||||||
|
'a922a0a62992f73b3d1b73106a84ad85' +
|
||||||
|
'e9b86a7b6005ea868337ff2d20a7f5fb' +
|
||||||
|
'd4cd10b0be49a68da2b2e0dc0ad8960f',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) {
|
||||||
|
const v = VECTORS_RFC8032_CTX[i];
|
||||||
|
should(`RFC8032ctx/${i}`, () => {
|
||||||
|
deepStrictEqual(hex(ed25519ctx.getPublicKey(v.secretKey)), v.publicKey);
|
||||||
|
deepStrictEqual(hex(ed25519ctx.sign(v.message, v.secretKey, v.context)), v.signature);
|
||||||
|
deepStrictEqual(ed25519ctx.verify(v.signature, v.message, v.publicKey, v.context), true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const VECTORS_RFC8032_PH = [
|
||||||
|
{
|
||||||
|
secretKey: '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42',
|
||||||
|
publicKey: 'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf',
|
||||||
|
message: '616263',
|
||||||
|
signature:
|
||||||
|
'98a70222f0b8121aa9d30f813d683f80' +
|
||||||
|
'9e462b469c7ff87639499bb94e6dae41' +
|
||||||
|
'31f85042463c2a355a2003d062adf5aa' +
|
||||||
|
'a10b8c61e636062aaad11c2a26083406',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
|
||||||
|
const v = VECTORS_RFC8032_PH[i];
|
||||||
|
should(`RFC8032ph/${i}`, () => {
|
||||||
|
deepStrictEqual(hex(ed25519ph.getPublicKey(v.secretKey)), v.publicKey);
|
||||||
|
deepStrictEqual(hex(ed25519ph.sign(v.message, v.secretKey)), v.signature);
|
||||||
|
deepStrictEqual(ed25519ph.verify(v.signature, v.message, v.publicKey), true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
should('X25519 base point', () => {
|
||||||
|
const { y } = ed25519.Point.BASE;
|
||||||
|
const u = ed25519.utils.mod((y + 1n) * ed25519.utils.invert(1n - y, ed25519.CURVE.P));
|
||||||
|
deepStrictEqual(hex(numberToBytesLE(u, 32)), x25519.Gu);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESM is broken.
|
||||||
|
import url from 'url';
|
||||||
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
should.run();
|
||||||
|
}
|
||||||
1024
curve-definitions/test/ed25519/vectors.txt
Normal file
1024
curve-definitions/test/ed25519/vectors.txt
Normal file
File diff suppressed because it is too large
Load Diff
1178
curve-definitions/test/ed25519/zip215.json
Normal file
1178
curve-definitions/test/ed25519/zip215.json
Normal file
File diff suppressed because it is too large
Load Diff
664
curve-definitions/test/ed448.test.js
Normal file
664
curve-definitions/test/ed448.test.js
Normal file
@@ -0,0 +1,664 @@
|
|||||||
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
|
import { should } from 'micro-should';
|
||||||
|
import * as fc from 'fast-check';
|
||||||
|
import { ed448, ed448ph, x448 } from '../lib/ed448.js';
|
||||||
|
import { hexToBytes, bytesToHex, randomBytes } from '@noble/hashes/utils';
|
||||||
|
import { numberToBytesLE } from '@noble/curves/utils';
|
||||||
|
import { default as ed448vectors } from './wycheproof/ed448_test.json' assert { type: 'json' };
|
||||||
|
import { default as x448vectors } from './wycheproof/x448_test.json' assert { type: 'json' };
|
||||||
|
|
||||||
|
const ed = ed448;
|
||||||
|
const hex = bytesToHex;
|
||||||
|
ed.utils.precompute(4);
|
||||||
|
|
||||||
|
should(`Basic`, () => {
|
||||||
|
const G1 = ed.Point.BASE;
|
||||||
|
deepStrictEqual(
|
||||||
|
G1.x,
|
||||||
|
224580040295924300187604334099896036246789641632564134246125461686950415467406032909029192869357953282578032075146446173674602635247710n
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
G1.y,
|
||||||
|
298819210078481492676017930443930673437544040154080242095928241372331506189835876003536878655418784733982303233503462500531545062832660n
|
||||||
|
);
|
||||||
|
const G2 = ed.Point.BASE.multiply(2n);
|
||||||
|
deepStrictEqual(
|
||||||
|
G2.x,
|
||||||
|
484559149530404593699549205258669689569094240458212040187660132787056912146709081364401144455726350866276831544947397859048262938744149n
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
G2.y,
|
||||||
|
494088759867433727674302672526735089350544552303727723746126484473087719117037293890093462157703888342865036477787453078312060500281069n
|
||||||
|
);
|
||||||
|
const G3 = ed.Point.BASE.multiply(3n);
|
||||||
|
deepStrictEqual(
|
||||||
|
G3.x,
|
||||||
|
23839778817283171003887799738662344287085130522697782688245073320169861206004018274567429238677677920280078599146891901463786155880335n
|
||||||
|
);
|
||||||
|
deepStrictEqual(
|
||||||
|
G3.y,
|
||||||
|
636046652612779686502873775776967954190574036985351036782021535703553242737829645273154208057988851307101009474686328623630835377952508n
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Basic/decompress', () => {
|
||||||
|
const G1 = ed.Point.BASE;
|
||||||
|
const G2 = ed.Point.BASE.multiply(2n);
|
||||||
|
const G3 = ed.Point.BASE.multiply(3n);
|
||||||
|
const points = [G1, G2, G3];
|
||||||
|
const getXY = (p) => ({ x: p.x, y: p.y });
|
||||||
|
for (const p of points) deepStrictEqual(getXY(ed.Point.fromHex(p.toHex())), getXY(p));
|
||||||
|
});
|
||||||
|
|
||||||
|
const VECTORS_RFC8032 = [
|
||||||
|
{
|
||||||
|
secretKey:
|
||||||
|
'6c82a562cb808d10d632be89c8513ebf' +
|
||||||
|
'6c929f34ddfa8c9f63c9960ef6e348a3' +
|
||||||
|
'528c8a3fcc2f044e39a3fc5b94492f8f' +
|
||||||
|
'032e7549a20098f95b',
|
||||||
|
publicKey:
|
||||||
|
'5fd7449b59b461fd2ce787ec616ad46a' +
|
||||||
|
'1da1342485a70e1f8a0ea75d80e96778' +
|
||||||
|
'edf124769b46c7061bd6783df1e50f6c' +
|
||||||
|
'd1fa1abeafe8256180',
|
||||||
|
message: '',
|
||||||
|
signature:
|
||||||
|
'533a37f6bbe457251f023c0d88f976ae' +
|
||||||
|
'2dfb504a843e34d2074fd823d41a591f' +
|
||||||
|
'2b233f034f628281f2fd7a22ddd47d78' +
|
||||||
|
'28c59bd0a21bfd3980ff0d2028d4b18a' +
|
||||||
|
'9df63e006c5d1c2d345b925d8dc00b41' +
|
||||||
|
'04852db99ac5c7cdda8530a113a0f4db' +
|
||||||
|
'b61149f05a7363268c71d95808ff2e65' +
|
||||||
|
'2600',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secretKey:
|
||||||
|
'c4eab05d357007c632f3dbb48489924d' +
|
||||||
|
'552b08fe0c353a0d4a1f00acda2c463a' +
|
||||||
|
'fbea67c5e8d2877c5e3bc397a659949e' +
|
||||||
|
'f8021e954e0a12274e',
|
||||||
|
publicKey:
|
||||||
|
'43ba28f430cdff456ae531545f7ecd0a' +
|
||||||
|
'c834a55d9358c0372bfa0c6c6798c086' +
|
||||||
|
'6aea01eb00742802b8438ea4cb82169c' +
|
||||||
|
'235160627b4c3a9480',
|
||||||
|
|
||||||
|
message: '03',
|
||||||
|
signature:
|
||||||
|
'26b8f91727bd62897af15e41eb43c377' +
|
||||||
|
'efb9c610d48f2335cb0bd0087810f435' +
|
||||||
|
'2541b143c4b981b7e18f62de8ccdf633' +
|
||||||
|
'fc1bf037ab7cd779805e0dbcc0aae1cb' +
|
||||||
|
'cee1afb2e027df36bc04dcecbf154336' +
|
||||||
|
'c19f0af7e0a6472905e799f1953d2a0f' +
|
||||||
|
'f3348ab21aa4adafd1d234441cf807c0' +
|
||||||
|
'3a00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secretKey:
|
||||||
|
'cd23d24f714274e744343237b93290f5' +
|
||||||
|
'11f6425f98e64459ff203e8985083ffd' +
|
||||||
|
'f60500553abc0e05cd02184bdb89c4cc' +
|
||||||
|
'd67e187951267eb328',
|
||||||
|
publicKey:
|
||||||
|
'dcea9e78f35a1bf3499a831b10b86c90' +
|
||||||
|
'aac01cd84b67a0109b55a36e9328b1e3' +
|
||||||
|
'65fce161d71ce7131a543ea4cb5f7e9f' +
|
||||||
|
'1d8b00696447001400',
|
||||||
|
message: '0c3e544074ec63b0265e0c',
|
||||||
|
signature:
|
||||||
|
'1f0a8888ce25e8d458a21130879b840a' +
|
||||||
|
'9089d999aaba039eaf3e3afa090a09d3' +
|
||||||
|
'89dba82c4ff2ae8ac5cdfb7c55e94d5d' +
|
||||||
|
'961a29fe0109941e00b8dbdeea6d3b05' +
|
||||||
|
'1068df7254c0cdc129cbe62db2dc957d' +
|
||||||
|
'bb47b51fd3f213fb8698f064774250a5' +
|
||||||
|
'028961c9bf8ffd973fe5d5c206492b14' +
|
||||||
|
'0e00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secretKey:
|
||||||
|
'258cdd4ada32ed9c9ff54e63756ae582' +
|
||||||
|
'fb8fab2ac721f2c8e676a72768513d93' +
|
||||||
|
'9f63dddb55609133f29adf86ec9929dc' +
|
||||||
|
'cb52c1c5fd2ff7e21b',
|
||||||
|
publicKey:
|
||||||
|
'3ba16da0c6f2cc1f30187740756f5e79' +
|
||||||
|
'8d6bc5fc015d7c63cc9510ee3fd44adc' +
|
||||||
|
'24d8e968b6e46e6f94d19b945361726b' +
|
||||||
|
'd75e149ef09817f580',
|
||||||
|
message: '64a65f3cdedcdd66811e2915',
|
||||||
|
signature:
|
||||||
|
'7eeeab7c4e50fb799b418ee5e3197ff6' +
|
||||||
|
'bf15d43a14c34389b59dd1a7b1b85b4a' +
|
||||||
|
'e90438aca634bea45e3a2695f1270f07' +
|
||||||
|
'fdcdf7c62b8efeaf00b45c2c96ba457e' +
|
||||||
|
'b1a8bf075a3db28e5c24f6b923ed4ad7' +
|
||||||
|
'47c3c9e03c7079efb87cb110d3a99861' +
|
||||||
|
'e72003cbae6d6b8b827e4e6c143064ff' +
|
||||||
|
'3c00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secretKey:
|
||||||
|
'7ef4e84544236752fbb56b8f31a23a10' +
|
||||||
|
'e42814f5f55ca037cdcc11c64c9a3b29' +
|
||||||
|
'49c1bb60700314611732a6c2fea98eeb' +
|
||||||
|
'c0266a11a93970100e',
|
||||||
|
publicKey:
|
||||||
|
'b3da079b0aa493a5772029f0467baebe' +
|
||||||
|
'e5a8112d9d3a22532361da294f7bb381' +
|
||||||
|
'5c5dc59e176b4d9f381ca0938e13c6c0' +
|
||||||
|
'7b174be65dfa578e80',
|
||||||
|
message: '64a65f3cdedcdd66811e2915e7',
|
||||||
|
signature:
|
||||||
|
'6a12066f55331b6c22acd5d5bfc5d712' +
|
||||||
|
'28fbda80ae8dec26bdd306743c5027cb' +
|
||||||
|
'4890810c162c027468675ecf645a8317' +
|
||||||
|
'6c0d7323a2ccde2d80efe5a1268e8aca' +
|
||||||
|
'1d6fbc194d3f77c44986eb4ab4177919' +
|
||||||
|
'ad8bec33eb47bbb5fc6e28196fd1caf5' +
|
||||||
|
'6b4e7e0ba5519234d047155ac727a105' +
|
||||||
|
'3100',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secretKey:
|
||||||
|
'd65df341ad13e008567688baedda8e9d' +
|
||||||
|
'cdc17dc024974ea5b4227b6530e339bf' +
|
||||||
|
'f21f99e68ca6968f3cca6dfe0fb9f4fa' +
|
||||||
|
'b4fa135d5542ea3f01',
|
||||||
|
publicKey:
|
||||||
|
'df9705f58edbab802c7f8363cfe5560a' +
|
||||||
|
'b1c6132c20a9f1dd163483a26f8ac53a' +
|
||||||
|
'39d6808bf4a1dfbd261b099bb03b3fb5' +
|
||||||
|
'0906cb28bd8a081f00',
|
||||||
|
message:
|
||||||
|
'bd0f6a3747cd561bdddf4640a332461a' +
|
||||||
|
'4a30a12a434cd0bf40d766d9c6d458e5' +
|
||||||
|
'512204a30c17d1f50b5079631f64eb31' +
|
||||||
|
'12182da3005835461113718d1a5ef944',
|
||||||
|
signature:
|
||||||
|
'554bc2480860b49eab8532d2a533b7d5' +
|
||||||
|
'78ef473eeb58c98bb2d0e1ce488a98b1' +
|
||||||
|
'8dfde9b9b90775e67f47d4a1c3482058' +
|
||||||
|
'efc9f40d2ca033a0801b63d45b3b722e' +
|
||||||
|
'f552bad3b4ccb667da350192b61c508c' +
|
||||||
|
'f7b6b5adadc2c8d9a446ef003fb05cba' +
|
||||||
|
'5f30e88e36ec2703b349ca229c267083' +
|
||||||
|
'3900',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secretKey:
|
||||||
|
'2ec5fe3c17045abdb136a5e6a913e32a' +
|
||||||
|
'b75ae68b53d2fc149b77e504132d3756' +
|
||||||
|
'9b7e766ba74a19bd6162343a21c8590a' +
|
||||||
|
'a9cebca9014c636df5',
|
||||||
|
publicKey:
|
||||||
|
'79756f014dcfe2079f5dd9e718be4171' +
|
||||||
|
'e2ef2486a08f25186f6bff43a9936b9b' +
|
||||||
|
'fe12402b08ae65798a3d81e22e9ec80e' +
|
||||||
|
'7690862ef3d4ed3a00',
|
||||||
|
message:
|
||||||
|
'15777532b0bdd0d1389f636c5f6b9ba7' +
|
||||||
|
'34c90af572877e2d272dd078aa1e567c' +
|
||||||
|
'fa80e12928bb542330e8409f31745041' +
|
||||||
|
'07ecd5efac61ae7504dabe2a602ede89' +
|
||||||
|
'e5cca6257a7c77e27a702b3ae39fc769' +
|
||||||
|
'fc54f2395ae6a1178cab4738e543072f' +
|
||||||
|
'c1c177fe71e92e25bf03e4ecb72f47b6' +
|
||||||
|
'4d0465aaea4c7fad372536c8ba516a60' +
|
||||||
|
'39c3c2a39f0e4d832be432dfa9a706a6' +
|
||||||
|
'e5c7e19f397964ca4258002f7c0541b5' +
|
||||||
|
'90316dbc5622b6b2a6fe7a4abffd9610' +
|
||||||
|
'5eca76ea7b98816af0748c10df048ce0' +
|
||||||
|
'12d901015a51f189f3888145c03650aa' +
|
||||||
|
'23ce894c3bd889e030d565071c59f409' +
|
||||||
|
'a9981b51878fd6fc110624dcbcde0bf7' +
|
||||||
|
'a69ccce38fabdf86f3bef6044819de11',
|
||||||
|
signature:
|
||||||
|
'c650ddbb0601c19ca11439e1640dd931' +
|
||||||
|
'f43c518ea5bea70d3dcde5f4191fe53f' +
|
||||||
|
'00cf966546b72bcc7d58be2b9badef28' +
|
||||||
|
'743954e3a44a23f880e8d4f1cfce2d7a' +
|
||||||
|
'61452d26da05896f0a50da66a239a8a1' +
|
||||||
|
'88b6d825b3305ad77b73fbac0836ecc6' +
|
||||||
|
'0987fd08527c1a8e80d5823e65cafe2a' +
|
||||||
|
'3d00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secretKey:
|
||||||
|
'872d093780f5d3730df7c212664b37b8' +
|
||||||
|
'a0f24f56810daa8382cd4fa3f77634ec' +
|
||||||
|
'44dc54f1c2ed9bea86fafb7632d8be19' +
|
||||||
|
'9ea165f5ad55dd9ce8',
|
||||||
|
publicKey:
|
||||||
|
'a81b2e8a70a5ac94ffdbcc9badfc3feb' +
|
||||||
|
'0801f258578bb114ad44ece1ec0e799d' +
|
||||||
|
'a08effb81c5d685c0c56f64eecaef8cd' +
|
||||||
|
'f11cc38737838cf400',
|
||||||
|
message:
|
||||||
|
'6ddf802e1aae4986935f7f981ba3f035' +
|
||||||
|
'1d6273c0a0c22c9c0e8339168e675412' +
|
||||||
|
'a3debfaf435ed651558007db4384b650' +
|
||||||
|
'fcc07e3b586a27a4f7a00ac8a6fec2cd' +
|
||||||
|
'86ae4bf1570c41e6a40c931db27b2faa' +
|
||||||
|
'15a8cedd52cff7362c4e6e23daec0fbc' +
|
||||||
|
'3a79b6806e316efcc7b68119bf46bc76' +
|
||||||
|
'a26067a53f296dafdbdc11c77f7777e9' +
|
||||||
|
'72660cf4b6a9b369a6665f02e0cc9b6e' +
|
||||||
|
'dfad136b4fabe723d2813db3136cfde9' +
|
||||||
|
'b6d044322fee2947952e031b73ab5c60' +
|
||||||
|
'3349b307bdc27bc6cb8b8bbd7bd32321' +
|
||||||
|
'9b8033a581b59eadebb09b3c4f3d2277' +
|
||||||
|
'd4f0343624acc817804728b25ab79717' +
|
||||||
|
'2b4c5c21a22f9c7839d64300232eb66e' +
|
||||||
|
'53f31c723fa37fe387c7d3e50bdf9813' +
|
||||||
|
'a30e5bb12cf4cd930c40cfb4e1fc6225' +
|
||||||
|
'92a49588794494d56d24ea4b40c89fc0' +
|
||||||
|
'596cc9ebb961c8cb10adde976a5d602b' +
|
||||||
|
'1c3f85b9b9a001ed3c6a4d3b1437f520' +
|
||||||
|
'96cd1956d042a597d561a596ecd3d173' +
|
||||||
|
'5a8d570ea0ec27225a2c4aaff26306d1' +
|
||||||
|
'526c1af3ca6d9cf5a2c98f47e1c46db9' +
|
||||||
|
'a33234cfd4d81f2c98538a09ebe76998' +
|
||||||
|
'd0d8fd25997c7d255c6d66ece6fa56f1' +
|
||||||
|
'1144950f027795e653008f4bd7ca2dee' +
|
||||||
|
'85d8e90f3dc315130ce2a00375a318c7' +
|
||||||
|
'c3d97be2c8ce5b6db41a6254ff264fa6' +
|
||||||
|
'155baee3b0773c0f497c573f19bb4f42' +
|
||||||
|
'40281f0b1f4f7be857a4e59d416c06b4' +
|
||||||
|
'c50fa09e1810ddc6b1467baeac5a3668' +
|
||||||
|
'd11b6ecaa901440016f389f80acc4db9' +
|
||||||
|
'77025e7f5924388c7e340a732e554440' +
|
||||||
|
'e76570f8dd71b7d640b3450d1fd5f041' +
|
||||||
|
'0a18f9a3494f707c717b79b4bf75c984' +
|
||||||
|
'00b096b21653b5d217cf3565c9597456' +
|
||||||
|
'f70703497a078763829bc01bb1cbc8fa' +
|
||||||
|
'04eadc9a6e3f6699587a9e75c94e5bab' +
|
||||||
|
'0036e0b2e711392cff0047d0d6b05bd2' +
|
||||||
|
'a588bc109718954259f1d86678a579a3' +
|
||||||
|
'120f19cfb2963f177aeb70f2d4844826' +
|
||||||
|
'262e51b80271272068ef5b3856fa8535' +
|
||||||
|
'aa2a88b2d41f2a0e2fda7624c2850272' +
|
||||||
|
'ac4a2f561f8f2f7a318bfd5caf969614' +
|
||||||
|
'9e4ac824ad3460538fdc25421beec2cc' +
|
||||||
|
'6818162d06bbed0c40a387192349db67' +
|
||||||
|
'a118bada6cd5ab0140ee273204f628aa' +
|
||||||
|
'd1c135f770279a651e24d8c14d75a605' +
|
||||||
|
'9d76b96a6fd857def5e0b354b27ab937' +
|
||||||
|
'a5815d16b5fae407ff18222c6d1ed263' +
|
||||||
|
'be68c95f32d908bd895cd76207ae7264' +
|
||||||
|
'87567f9a67dad79abec316f683b17f2d' +
|
||||||
|
'02bf07e0ac8b5bc6162cf94697b3c27c' +
|
||||||
|
'd1fea49b27f23ba2901871962506520c' +
|
||||||
|
'392da8b6ad0d99f7013fbc06c2c17a56' +
|
||||||
|
'9500c8a7696481c1cd33e9b14e40b82e' +
|
||||||
|
'79a5f5db82571ba97bae3ad3e0479515' +
|
||||||
|
'bb0e2b0f3bfcd1fd33034efc6245eddd' +
|
||||||
|
'7ee2086ddae2600d8ca73e214e8c2b0b' +
|
||||||
|
'db2b047c6a464a562ed77b73d2d841c4' +
|
||||||
|
'b34973551257713b753632efba348169' +
|
||||||
|
'abc90a68f42611a40126d7cb21b58695' +
|
||||||
|
'568186f7e569d2ff0f9e745d0487dd2e' +
|
||||||
|
'b997cafc5abf9dd102e62ff66cba87',
|
||||||
|
signature:
|
||||||
|
'e301345a41a39a4d72fff8df69c98075' +
|
||||||
|
'a0cc082b802fc9b2b6bc503f926b65bd' +
|
||||||
|
'df7f4c8f1cb49f6396afc8a70abe6d8a' +
|
||||||
|
'ef0db478d4c6b2970076c6a0484fe76d' +
|
||||||
|
'76b3a97625d79f1ce240e7c576750d29' +
|
||||||
|
'5528286f719b413de9ada3e8eb78ed57' +
|
||||||
|
'3603ce30d8bb761785dc30dbc320869e' +
|
||||||
|
'1a00',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < VECTORS_RFC8032.length; i++) {
|
||||||
|
const v = VECTORS_RFC8032[i];
|
||||||
|
should(`RFC8032/${i}`, () => {
|
||||||
|
deepStrictEqual(hex(ed.getPublicKey(v.secretKey)), v.publicKey);
|
||||||
|
deepStrictEqual(hex(ed.sign(v.message, v.secretKey)), v.signature);
|
||||||
|
deepStrictEqual(ed.verify(v.signature, v.message, v.publicKey), true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
should('ed448/should not accept >57byte private keys', async () => {
|
||||||
|
const invalidPriv =
|
||||||
|
100000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800073278156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n;
|
||||||
|
throws(() => ed.getPublicKey(invalidPriv));
|
||||||
|
});
|
||||||
|
|
||||||
|
function to57Bytes(numOrStr) {
|
||||||
|
let hex = typeof numOrStr === 'string' ? numOrStr : numOrStr.toString(16);
|
||||||
|
return hexToBytes(hex.padStart(114, '0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
should('ed448/should verify recent signature', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(
|
||||||
|
fc.hexaString({ minLength: 2, maxLength: 57 }),
|
||||||
|
fc.bigInt(2n, ed.CURVE.n),
|
||||||
|
(message, privateKey) => {
|
||||||
|
const publicKey = ed.getPublicKey(to57Bytes(privateKey));
|
||||||
|
const signature = ed.sign(to57Bytes(message), to57Bytes(privateKey));
|
||||||
|
deepStrictEqual(publicKey.length, 57);
|
||||||
|
deepStrictEqual(signature.length, 114);
|
||||||
|
deepStrictEqual(ed.verify(signature, to57Bytes(message), publicKey), true);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{ numRuns: 5 }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
should('ed448/should not verify signature with wrong message', () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(
|
||||||
|
fc.array(fc.integer({ min: 0x00, max: 0xff })),
|
||||||
|
fc.array(fc.integer({ min: 0x00, max: 0xff })),
|
||||||
|
fc.bigInt(1n, ed.CURVE.n),
|
||||||
|
(bytes, wrongBytes, privateKey) => {
|
||||||
|
const message = new Uint8Array(bytes);
|
||||||
|
const wrongMessage = new Uint8Array(wrongBytes);
|
||||||
|
const priv = to57Bytes(privateKey);
|
||||||
|
const publicKey = ed.getPublicKey(priv);
|
||||||
|
const signature = ed.sign(message, priv);
|
||||||
|
deepStrictEqual(
|
||||||
|
ed.verify(signature, wrongMessage, publicKey),
|
||||||
|
bytes.toString() === wrongBytes.toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{ numRuns: 5 }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const privKey = to57Bytes('a665a45920422f9d417e4867ef');
|
||||||
|
const msg = hexToBytes('874f9960c5d2b7a9b5fad383e1ba44719ebb743a');
|
||||||
|
const wrongMsg = hexToBytes('589d8c7f1da0a24bc07b7381ad48b1cfc211af1c');
|
||||||
|
should('ed25519/basic methods/should sign and verify', () => {
|
||||||
|
const publicKey = ed.getPublicKey(privKey);
|
||||||
|
const signature = ed.sign(msg, privKey);
|
||||||
|
deepStrictEqual(ed.verify(signature, msg, publicKey), true);
|
||||||
|
});
|
||||||
|
should('ed25519/basic methods/should not verify signature with wrong public key', () => {
|
||||||
|
const publicKey = ed.getPublicKey(12);
|
||||||
|
const signature = ed.sign(msg, privKey);
|
||||||
|
deepStrictEqual(ed.verify(signature, msg, publicKey), false);
|
||||||
|
});
|
||||||
|
should('ed25519/basic methods/should not verify signature with wrong hash', () => {
|
||||||
|
const publicKey = ed.getPublicKey(privKey);
|
||||||
|
const signature = ed.sign(msg, privKey);
|
||||||
|
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('ed25519/sync methods/should sign and verify', () => {
|
||||||
|
const publicKey = ed.getPublicKey(privKey);
|
||||||
|
const signature = ed.sign(msg, privKey);
|
||||||
|
deepStrictEqual(ed.verify(signature, msg, publicKey), true);
|
||||||
|
});
|
||||||
|
should('ed25519/sync methods/should not verify signature with wrong public key', async () => {
|
||||||
|
const publicKey = ed.getPublicKey(12);
|
||||||
|
const signature = ed.sign(msg, privKey);
|
||||||
|
deepStrictEqual(ed.verify(signature, msg, publicKey), false);
|
||||||
|
});
|
||||||
|
should('ed25519/sync methods/should not verify signature with wrong hash', async () => {
|
||||||
|
const publicKey = ed.getPublicKey(privKey);
|
||||||
|
const signature = ed.sign(msg, privKey);
|
||||||
|
deepStrictEqual(ed.verify(signature, wrongMsg, publicKey), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('ed25519/BASE_POINT.multiply()/should throw Point#multiply on TEST 5', () => {
|
||||||
|
for (const num of [0n, 0, -1n, -1, 1.1]) {
|
||||||
|
throws(() => ed.Point.BASE.multiply(num));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('input immutability: sign/verify are immutable', () => {
|
||||||
|
const privateKey = ed.utils.randomPrivateKey();
|
||||||
|
const publicKey = ed.getPublicKey(privateKey);
|
||||||
|
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
let payload = randomBytes(100);
|
||||||
|
let signature = ed.sign(payload, privateKey);
|
||||||
|
if (!ed.verify(signature, payload, publicKey)) {
|
||||||
|
throw new Error('Signature verification failed');
|
||||||
|
}
|
||||||
|
const signatureCopy = Buffer.alloc(signature.byteLength);
|
||||||
|
signatureCopy.set(signature, 0); // <-- breaks
|
||||||
|
payload = payload.slice();
|
||||||
|
signature = signature.slice();
|
||||||
|
|
||||||
|
if (!ed.verify(signatureCopy, payload, publicKey))
|
||||||
|
throw new Error('Copied signature verification failed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
for (let g = 0; g < ed448vectors.testGroups.length; g++) {
|
||||||
|
const group = ed448vectors.testGroups[g];
|
||||||
|
const key = group.key;
|
||||||
|
should(`Wycheproof/ED448(${g}, public)`, () => {
|
||||||
|
deepStrictEqual(hex(ed.getPublicKey(key.sk)), key.pk);
|
||||||
|
});
|
||||||
|
should(`Wycheproof/ED448`, () => {
|
||||||
|
for (let i = 0; i < group.tests.length; i++) {
|
||||||
|
const v = group.tests[i];
|
||||||
|
const index = `${g}/${i} ${v.comment}`;
|
||||||
|
if (v.result === 'valid' || v.result === 'acceptable') {
|
||||||
|
deepStrictEqual(hex(ed.sign(v.msg, key.sk)), v.sig, index);
|
||||||
|
deepStrictEqual(ed.verify(v.sig, v.msg, key.pk), true, index);
|
||||||
|
} else if (v.result === 'invalid') {
|
||||||
|
let failed = false;
|
||||||
|
try {
|
||||||
|
failed = !ed.verify(v.sig, v.msg, key.pk);
|
||||||
|
} catch (error) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
deepStrictEqual(failed, true, index);
|
||||||
|
} else throw new Error('unknown test result');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECDH
|
||||||
|
const rfc7748Mul = [
|
||||||
|
{
|
||||||
|
scalar:
|
||||||
|
'3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3',
|
||||||
|
u: '06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086',
|
||||||
|
outputU:
|
||||||
|
'ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scalar:
|
||||||
|
'203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b4371282dd2c8d5be3095f',
|
||||||
|
u: '0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfbde71ce8d157db',
|
||||||
|
outputU:
|
||||||
|
'884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for (let i = 0; i < rfc7748Mul.length; i++) {
|
||||||
|
const v = rfc7748Mul[i];
|
||||||
|
should(`RFC7748: scalarMult (${i})`, () => {
|
||||||
|
deepStrictEqual(hex(x448.scalarMult(v.u, v.scalar)), v.outputU);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const rfc7748Iter = [
|
||||||
|
{
|
||||||
|
scalar:
|
||||||
|
'3f482c8a9f19b01e6c46ee9711d9dc14fd4bf67af30765c2ae2b846a4d23a8cd0db897086239492caf350b51f833868b9bc2b3bca9cf4113',
|
||||||
|
iters: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scalar:
|
||||||
|
'aa3b4749d55b9daf1e5b00288826c467274ce3ebbdd5c17b975e09d4af6c67cf10d087202db88286e2b79fceea3ec353ef54faa26e219f38',
|
||||||
|
iters: 1000,
|
||||||
|
},
|
||||||
|
// { scalar: '077f453681caca3693198420bbe515cae0002472519b3e67661a7e89cab94695c8f4bcd66e61b9b9c946da8d524de3d69bd9d9d66b997e37', iters: 1000000 },
|
||||||
|
];
|
||||||
|
for (let i = 0; i < rfc7748Iter.length; i++) {
|
||||||
|
const { scalar, iters } = rfc7748Iter[i];
|
||||||
|
should(`RFC7748: scalarMult iteration (${i})`, () => {
|
||||||
|
let k = x448.Gu;
|
||||||
|
for (let i = 0, u = k; i < iters; i++) [k, u] = [x448.scalarMult(u, k), k];
|
||||||
|
deepStrictEqual(hex(k), scalar);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
should('RFC7748 getSharedKey', () => {
|
||||||
|
const alicePrivate =
|
||||||
|
'9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b';
|
||||||
|
const alicePublic =
|
||||||
|
'9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0';
|
||||||
|
const bobPrivate =
|
||||||
|
'1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d';
|
||||||
|
const bobPublic =
|
||||||
|
'3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609';
|
||||||
|
const shared =
|
||||||
|
'07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d';
|
||||||
|
deepStrictEqual(alicePublic, hex(x448.getPublicKey(alicePrivate)));
|
||||||
|
deepStrictEqual(bobPublic, hex(x448.getPublicKey(bobPrivate)));
|
||||||
|
deepStrictEqual(hex(x448.scalarMult(bobPublic, alicePrivate)), shared);
|
||||||
|
deepStrictEqual(hex(x448.scalarMult(alicePublic, bobPrivate)), shared);
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
const group = x448vectors.testGroups[0];
|
||||||
|
should(`Wycheproof/X448`, () => {
|
||||||
|
for (let i = 0; i < group.tests.length; i++) {
|
||||||
|
const v = group.tests[i];
|
||||||
|
const index = `(${i}, ${v.result}) ${v.comment}`;
|
||||||
|
if (v.result === 'valid' || v.result === 'acceptable') {
|
||||||
|
try {
|
||||||
|
const shared = hex(x448.scalarMult(v.public, v.private));
|
||||||
|
deepStrictEqual(shared, v.shared, index);
|
||||||
|
} catch (e) {
|
||||||
|
// We are more strict
|
||||||
|
if (e.message.includes('Expected valid scalar')) return;
|
||||||
|
if (e.message.includes('Invalid private or public key received')) return;
|
||||||
|
if (e.message.includes('Expected 56 bytes')) return;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else if (v.result === 'invalid') {
|
||||||
|
let failed = false;
|
||||||
|
try {
|
||||||
|
x448.scalarMult(v.public, v.private);
|
||||||
|
} catch (error) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
deepStrictEqual(failed, true, index);
|
||||||
|
} else throw new Error('unknown test result');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// should('X448: should convert base point to montgomery using fromPoint', () => {
|
||||||
|
// deepStrictEqual(
|
||||||
|
// hex(ed.montgomeryCurve.UfromPoint(ed.Point.BASE)),
|
||||||
|
// ed.montgomeryCurve.BASE_POINT_U
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
// should('X448/getSharedSecret() should be commutative', async () => {
|
||||||
|
// for (let i = 0; i < 512; i++) {
|
||||||
|
// const asec = ed.utils.randomPrivateKey();
|
||||||
|
// const apub = ed.getPublicKey(asec);
|
||||||
|
// const bsec = ed.utils.randomPrivateKey();
|
||||||
|
// const bpub = ed.getPublicKey(bsec);
|
||||||
|
// try {
|
||||||
|
// deepStrictEqual(ed.getSharedSecret(asec, bpub), ed.getSharedSecret(bsec, apub));
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('not commutative', { asec, apub, bsec, bpub });
|
||||||
|
// throw error;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
const VECTORS_RFC8032_CTX = [
|
||||||
|
{
|
||||||
|
secretKey:
|
||||||
|
'c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463afbea67c5e8d2877c5e3bc397a659949ef8021e954e0a12274e',
|
||||||
|
publicKey:
|
||||||
|
'43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c0866aea01eb00742802b8438ea4cb82169c235160627b4c3a9480',
|
||||||
|
message: '03',
|
||||||
|
context: '666f6f',
|
||||||
|
signature:
|
||||||
|
'd4f8f6131770dd46f40867d6fd5d5055' +
|
||||||
|
'de43541f8c5e35abbcd001b32a89f7d2' +
|
||||||
|
'151f7647f11d8ca2ae279fb842d60721' +
|
||||||
|
'7fce6e042f6815ea000c85741de5c8da' +
|
||||||
|
'1144a6a1aba7f96de42505d7a7298524' +
|
||||||
|
'fda538fccbbb754f578c1cad10d54d0d' +
|
||||||
|
'5428407e85dcbc98a49155c13764e66c' +
|
||||||
|
'3c00',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < VECTORS_RFC8032_CTX.length; i++) {
|
||||||
|
const v = VECTORS_RFC8032_CTX[i];
|
||||||
|
should(`RFC8032ctx/${i}`, () => {
|
||||||
|
deepStrictEqual(hex(ed.getPublicKey(v.secretKey)), v.publicKey);
|
||||||
|
deepStrictEqual(hex(ed.sign(v.message, v.secretKey, v.context)), v.signature);
|
||||||
|
deepStrictEqual(ed.verify(v.signature, v.message, v.publicKey, v.context), true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const VECTORS_RFC8032_PH = [
|
||||||
|
{
|
||||||
|
secretKey:
|
||||||
|
'833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42ef7822e0d5104127dc05d6dbefde69e3ab2cec7c867c6e2c49',
|
||||||
|
publicKey:
|
||||||
|
'259b71c19f83ef77a7abd26524cbdb3161b590a48f7d17de3ee0ba9c52beb743c09428a131d6b1b57303d90d8132c276d5ed3d5d01c0f53880',
|
||||||
|
message: '616263',
|
||||||
|
signature:
|
||||||
|
'822f6901f7480f3d5f562c592994d969' +
|
||||||
|
'3602875614483256505600bbc281ae38' +
|
||||||
|
'1f54d6bce2ea911574932f52a4e6cadd' +
|
||||||
|
'78769375ec3ffd1b801a0d9b3f4030cd' +
|
||||||
|
'433964b6457ea39476511214f97469b5' +
|
||||||
|
'7dd32dbc560a9a94d00bff07620464a3' +
|
||||||
|
'ad203df7dc7ce360c3cd3696d9d9fab9' +
|
||||||
|
'0f00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secretKey:
|
||||||
|
'833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42ef7822e0d5104127dc05d6dbefde69e3ab2cec7c867c6e2c49',
|
||||||
|
publicKey:
|
||||||
|
'259b71c19f83ef77a7abd26524cbdb3161b590a48f7d17de3ee0ba9c52beb743c09428a131d6b1b57303d90d8132c276d5ed3d5d01c0f53880',
|
||||||
|
message: '616263',
|
||||||
|
context: '666f6f',
|
||||||
|
signature:
|
||||||
|
'c32299d46ec8ff02b54540982814dce9' +
|
||||||
|
'a05812f81962b649d528095916a2aa48' +
|
||||||
|
'1065b1580423ef927ecf0af5888f90da' +
|
||||||
|
'0f6a9a85ad5dc3f280d91224ba9911a3' +
|
||||||
|
'653d00e484e2ce232521481c8658df30' +
|
||||||
|
'4bb7745a73514cdb9bf3e15784ab7128' +
|
||||||
|
'4f8d0704a608c54a6b62d97beb511d13' +
|
||||||
|
'2100',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < VECTORS_RFC8032_PH.length; i++) {
|
||||||
|
const v = VECTORS_RFC8032_PH[i];
|
||||||
|
should(`RFC8032ph/${i}`, () => {
|
||||||
|
deepStrictEqual(hex(ed448ph.getPublicKey(v.secretKey)), v.publicKey);
|
||||||
|
deepStrictEqual(hex(ed448ph.sign(v.message, v.secretKey, v.context)), v.signature);
|
||||||
|
deepStrictEqual(ed448ph.verify(v.signature, v.message, v.publicKey, v.context), true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
should('X448 base point', () => {
|
||||||
|
const { x, y } = ed448.Point.BASE;
|
||||||
|
const { P } = ed448.CURVE;
|
||||||
|
const invX = ed448.utils.invert(x * x, P); // x^2
|
||||||
|
const u = ed448.utils.mod(y * y * invX, P); // (y^2/x^2)
|
||||||
|
deepStrictEqual(hex(numberToBytesLE(u, 56)), x448.Gu);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESM is broken.
|
||||||
|
import url from 'url';
|
||||||
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
should.run();
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
import { should } from 'micro-should';
|
import { should } from 'micro-should';
|
||||||
|
|
||||||
|
// Should be first to catch obvious things
|
||||||
import './basic.test.js';
|
import './basic.test.js';
|
||||||
import './rfc6979.test.js';
|
import './nist.test.js';
|
||||||
|
import './ed448.test.js';
|
||||||
|
import './ed25519.test.js';
|
||||||
import './secp256k1.test.js';
|
import './secp256k1.test.js';
|
||||||
import './starknet/starknet.test.js';
|
import './stark/stark.test.js';
|
||||||
|
import './jubjub.test.js';
|
||||||
|
|
||||||
should.run();
|
should.run();
|
||||||
|
|||||||
74
curve-definitions/test/jubjub.test.js
Normal file
74
curve-definitions/test/jubjub.test.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { jubjub, findGroupHash } from '../lib/jubjub.js';
|
||||||
|
import { should } from 'micro-should';
|
||||||
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
|
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
|
||||||
|
|
||||||
|
const G_SPEND = new jubjub.ExtendedPoint(
|
||||||
|
0x055f1f24f0f0512287e51c3c5a0a6903fc0baf8711de9eafd7c0e66f69d8d2dbn,
|
||||||
|
0x566178b2505fdd52132a5007d80a04652842e78ffb376897588f406278214ed7n,
|
||||||
|
0x0141fafa1f11088a3b2007c14d652375888f3b37838ba6bdffae096741ceddfen,
|
||||||
|
0x12eada93c0b7d595f5f04f5ebfb4b7d033ef2884136475cab5e41ce17db5be9cn
|
||||||
|
);
|
||||||
|
const G_PROOF = new jubjub.ExtendedPoint(
|
||||||
|
0x0174d54ce9fad258a2f8a86a1deabf15c7a2b51106b0fbcd9d29020f78936f71n,
|
||||||
|
0x16871d6d877dcd222e4ec3bccb3f37cb1865a2d37dd3a5dcbc032a69b62b4445n,
|
||||||
|
0x57a3cd31e496d82bd4aa78bd5ecd751cfb76d54a5d3f4560866379f9fc11c9b3n,
|
||||||
|
0x42cc53f6b519d1f4f52c47ff1256463a616c2c2f49ffe77765481eca04c72081n
|
||||||
|
);
|
||||||
|
|
||||||
|
const getXY = (p) => ({ x: p.x, y: p.y });
|
||||||
|
|
||||||
|
should('toHex/fromHex', () => {
|
||||||
|
// More than field
|
||||||
|
throws(() =>
|
||||||
|
jubjub.Point.fromHex(
|
||||||
|
new Uint8Array([
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Multiplicative generator (sqrt == null), not on curve.
|
||||||
|
throws(() =>
|
||||||
|
jubjub.Point.fromHex(
|
||||||
|
new Uint8Array([
|
||||||
|
7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const tmp = jubjub.Point.fromHex(
|
||||||
|
new Uint8Array([
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
deepStrictEqual(tmp.x, 0x8d51ccce760304d0ec030002760300000001000000000000n);
|
||||||
|
deepStrictEqual(tmp.y, 0n);
|
||||||
|
|
||||||
|
const S = G_SPEND.toAffine().toRawBytes();
|
||||||
|
const S2 = G_SPEND.double().toAffine().toRawBytes();
|
||||||
|
const P = G_PROOF.toAffine().toRawBytes();
|
||||||
|
const P2 = G_PROOF.double().toAffine().toRawBytes();
|
||||||
|
const S_exp = jubjub.Point.fromHex(S);
|
||||||
|
const S2_exp = jubjub.Point.fromHex(S2);
|
||||||
|
const P_exp = jubjub.Point.fromHex(P);
|
||||||
|
const P2_exp = jubjub.Point.fromHex(P2);
|
||||||
|
deepStrictEqual(getXY(G_SPEND.toAffine()), getXY(S_exp));
|
||||||
|
deepStrictEqual(getXY(G_SPEND.double().toAffine()), getXY(S2_exp));
|
||||||
|
deepStrictEqual(getXY(G_PROOF.toAffine()), getXY(P_exp));
|
||||||
|
deepStrictEqual(getXY(G_PROOF.double().toAffine()), getXY(P2_exp));
|
||||||
|
});
|
||||||
|
|
||||||
|
should('Find generators', () => {
|
||||||
|
const spend = findGroupHash(new Uint8Array(), new Uint8Array([90, 99, 97, 115, 104, 95, 71, 95]));
|
||||||
|
const proof = findGroupHash(new Uint8Array(), new Uint8Array([90, 99, 97, 115, 104, 95, 72, 95]));
|
||||||
|
deepStrictEqual(getXY(spend.toAffine()), getXY(G_SPEND.toAffine()));
|
||||||
|
deepStrictEqual(getXY(proof.toAffine()), getXY(G_PROOF.toAffine()));
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESM is broken.
|
||||||
|
import url from 'url';
|
||||||
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
should.run();
|
||||||
|
}
|
||||||
387
curve-definitions/test/nist.test.js
Normal file
387
curve-definitions/test/nist.test.js
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
|
import { should } from 'micro-should';
|
||||||
|
import { secp192r1, P192 } from '../lib/p192.js';
|
||||||
|
import { secp224r1, P224 } from '../lib/p224.js';
|
||||||
|
import { secp256r1, P256 } from '../lib/p256.js';
|
||||||
|
import { secp384r1, P384 } from '../lib/p384.js';
|
||||||
|
import { secp521r1, P521 } from '../lib/p521.js';
|
||||||
|
import { secp256k1 } from '../lib/secp256k1.js';
|
||||||
|
import { hexToBytes, bytesToHex } from '@noble/curves/utils';
|
||||||
|
import { default as ecdsa } from './wycheproof/ecdsa_test.json' assert { type: 'json' };
|
||||||
|
import { default as ecdh } from './wycheproof/ecdh_test.json' assert { type: 'json' };
|
||||||
|
import { default as rfc6979 } from './fixtures/rfc6979.json' assert { type: 'json' };
|
||||||
|
|
||||||
|
const hex = bytesToHex;
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
const NIST = {
|
||||||
|
secp192r1, P192,
|
||||||
|
secp224r1, P224,
|
||||||
|
secp256r1, P256,
|
||||||
|
secp384r1, P384,
|
||||||
|
secp521r1, P521,
|
||||||
|
secp256k1,
|
||||||
|
};
|
||||||
|
|
||||||
|
should('Curve Fields', () => {
|
||||||
|
const vectors = {
|
||||||
|
secp192r1: 0xfffffffffffffffffffffffffffffffeffffffffffffffffn,
|
||||||
|
secp224r1: 0xffffffffffffffffffffffffffffffff000000000000000000000001n,
|
||||||
|
secp256r1: 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffffn,
|
||||||
|
secp256k1: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn,
|
||||||
|
secp384r1:
|
||||||
|
0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffffn,
|
||||||
|
secp521r1:
|
||||||
|
0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn,
|
||||||
|
};
|
||||||
|
for (const n in vectors) deepStrictEqual(NIST[n].CURVE.P, vectors[n]);
|
||||||
|
});
|
||||||
|
|
||||||
|
should('wychenproof ECDSA vectors', () => {
|
||||||
|
for (const group of ecdsa.testGroups) {
|
||||||
|
// Tested in secp256k1.test.js
|
||||||
|
if (group.key.curve === 'secp256k1') continue;
|
||||||
|
// We don't have SHA-224
|
||||||
|
if (group.key.curve === 'secp224r1' && group.sha === 'SHA-224') continue;
|
||||||
|
const CURVE = NIST[group.key.curve];
|
||||||
|
if (!CURVE) continue;
|
||||||
|
const pubKey = CURVE.Point.fromHex(group.key.uncompressed);
|
||||||
|
deepStrictEqual(pubKey.x, BigInt(`0x${group.key.wx}`));
|
||||||
|
deepStrictEqual(pubKey.y, BigInt(`0x${group.key.wy}`));
|
||||||
|
for (const test of group.tests) {
|
||||||
|
if (['Hash weaker than DL-group'].includes(test.comment)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const m = CURVE.CURVE.hash(hexToBytes(test.msg));
|
||||||
|
if (test.result === 'valid' || test.result === 'acceptable') {
|
||||||
|
try {
|
||||||
|
CURVE.Signature.fromDER(test.sig);
|
||||||
|
} catch (e) {
|
||||||
|
// Some test has invalid signature which we don't accept
|
||||||
|
if (e.message.includes('Invalid signature: incorrect length')) continue;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
const verified = CURVE.verify(test.sig, m, pubKey);
|
||||||
|
deepStrictEqual(verified, true, 'valid');
|
||||||
|
} else if (test.result === 'invalid') {
|
||||||
|
let failed = false;
|
||||||
|
try {
|
||||||
|
failed = !CURVE.verify(test.sig, m, pubKey);
|
||||||
|
} catch (error) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
deepStrictEqual(failed, true, 'invalid');
|
||||||
|
} else throw new Error('unknown test result');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
should('wychenproof ECDH vectors', () => {
|
||||||
|
for (const group of ecdh.testGroups) {
|
||||||
|
// // Tested in secp256k1.test.js
|
||||||
|
// if (group.key.curve === 'secp256k1') continue;
|
||||||
|
// We don't have SHA-224
|
||||||
|
const CURVE = NIST[group.curve];
|
||||||
|
if (!CURVE) continue;
|
||||||
|
for (const test of group.tests) {
|
||||||
|
if (test.result === 'valid' || test.result === 'acceptable') {
|
||||||
|
try {
|
||||||
|
const pub = CURVE.Point.fromHex(test.public);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message.includes('Point.fromHex: received invalid point.')) continue;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
const shared = CURVE.getSharedSecret(test.private, test.public);
|
||||||
|
deepStrictEqual(shared, test.shared, 'valid');
|
||||||
|
} else if (test.result === 'invalid') {
|
||||||
|
let failed = false;
|
||||||
|
try {
|
||||||
|
CURVE.getSharedSecret(test.private, test.public);
|
||||||
|
} catch (error) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
deepStrictEqual(failed, true, 'invalid');
|
||||||
|
} else throw new Error('unknown test result');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
import { default as ecdh_secp224r1_test } from './wycheproof/ecdh_secp224r1_test.json' assert { type: 'json' };
|
||||||
|
import { default as ecdh_secp256r1_test } from './wycheproof/ecdh_secp256r1_test.json' assert { type: 'json' };
|
||||||
|
import { default as ecdh_secp256k1_test } from './wycheproof/ecdh_secp256k1_test.json' assert { type: 'json' };
|
||||||
|
import { default as ecdh_secp384r1_test } from './wycheproof/ecdh_secp384r1_test.json' assert { type: 'json' };
|
||||||
|
import { default as ecdh_secp521r1_test } from './wycheproof/ecdh_secp521r1_test.json' assert { type: 'json' };
|
||||||
|
|
||||||
|
// More per curve tests
|
||||||
|
const WYCHEPROOF_ECDH = {
|
||||||
|
P224: {
|
||||||
|
curve: P224,
|
||||||
|
tests: [ecdh_secp224r1_test],
|
||||||
|
},
|
||||||
|
P256: {
|
||||||
|
curve: P256,
|
||||||
|
tests: [ecdh_secp256r1_test],
|
||||||
|
},
|
||||||
|
secp256k1: {
|
||||||
|
curve: secp256k1,
|
||||||
|
tests: [ecdh_secp256k1_test],
|
||||||
|
},
|
||||||
|
P384: {
|
||||||
|
curve: P384,
|
||||||
|
tests: [ecdh_secp384r1_test],
|
||||||
|
},
|
||||||
|
P521: {
|
||||||
|
curve: P521,
|
||||||
|
tests: [ecdh_secp521r1_test],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const name in WYCHEPROOF_ECDH) {
|
||||||
|
const { curve, tests } = WYCHEPROOF_ECDH[name];
|
||||||
|
for (let i = 0; i < tests.length; i++) {
|
||||||
|
const test = tests[i];
|
||||||
|
for (let j = 0; j < test.testGroups.length; j++) {
|
||||||
|
const group = test.testGroups[j];
|
||||||
|
should(`Wycheproof/ECDH ${name} (${i}/${j})`, () => {
|
||||||
|
for (const test of group.tests) {
|
||||||
|
if (test.result === 'valid' || test.result === 'acceptable') {
|
||||||
|
try {
|
||||||
|
const pub = curve.Point.fromHex(test.public);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message.includes('Point.fromHex: received invalid point.')) continue;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
const shared = curve.getSharedSecret(test.private, test.public);
|
||||||
|
deepStrictEqual(hex(shared), test.shared, 'valid');
|
||||||
|
} else if (test.result === 'invalid') {
|
||||||
|
let failed = false;
|
||||||
|
try {
|
||||||
|
curve.getSharedSecret(test.private, test.public);
|
||||||
|
} catch (error) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
deepStrictEqual(failed, true, 'invalid');
|
||||||
|
} else throw new Error('unknown test result');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests with custom hashes
|
||||||
|
import { default as secp224r1_sha224_test } from './wycheproof/ecdsa_secp224r1_sha224_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp224r1_sha256_test } from './wycheproof/ecdsa_secp224r1_sha256_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp224r1_sha3_224_test } from './wycheproof/ecdsa_secp224r1_sha3_224_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp224r1_sha3_256_test } from './wycheproof/ecdsa_secp224r1_sha3_256_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp224r1_sha3_512_test } from './wycheproof/ecdsa_secp224r1_sha3_512_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp224r1_sha512_test } from './wycheproof/ecdsa_secp224r1_sha512_test.json' assert { type: 'json' };
|
||||||
|
|
||||||
|
import { default as secp256k1_sha256_test } from './wycheproof/ecdsa_secp256k1_sha256_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp256k1_sha3_256_test } from './wycheproof/ecdsa_secp256k1_sha3_256_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp256k1_sha3_512_test } from './wycheproof/ecdsa_secp256k1_sha3_512_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp256k1_sha512_test } from './wycheproof/ecdsa_secp256k1_sha512_test.json' assert { type: 'json' };
|
||||||
|
|
||||||
|
import { default as secp256r1_sha256_test } from './wycheproof/ecdsa_secp256r1_sha256_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp256r1_sha3_256_test } from './wycheproof/ecdsa_secp256r1_sha3_256_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp256r1_sha3_512_test } from './wycheproof/ecdsa_secp256r1_sha3_512_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp256r1_sha512_test } from './wycheproof/ecdsa_secp256r1_sha512_test.json' assert { type: 'json' };
|
||||||
|
|
||||||
|
import { default as secp384r1_sha384_test } from './wycheproof/ecdsa_secp384r1_sha384_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp384r1_sha3_384_test } from './wycheproof/ecdsa_secp384r1_sha3_384_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp384r1_sha3_512_test } from './wycheproof/ecdsa_secp384r1_sha3_512_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp384r1_sha512_test } from './wycheproof/ecdsa_secp384r1_sha512_test.json' assert { type: 'json' };
|
||||||
|
|
||||||
|
import { default as secp521r1_sha3_512_test } from './wycheproof/ecdsa_secp521r1_sha3_512_test.json' assert { type: 'json' };
|
||||||
|
import { default as secp521r1_sha512_test } from './wycheproof/ecdsa_secp521r1_sha512_test.json' assert { type: 'json' };
|
||||||
|
|
||||||
|
import { sha3_224, sha3_256, sha3_384, sha3_512 } from '@noble/hashes/sha3';
|
||||||
|
import { sha512, sha384 } from '@noble/hashes/sha512';
|
||||||
|
import { sha256 } from '@noble/hashes/sha256';
|
||||||
|
|
||||||
|
const WYCHEPROOF_ECDSA = {
|
||||||
|
P224: {
|
||||||
|
curve: P224,
|
||||||
|
hashes: {
|
||||||
|
// sha224 not released yet
|
||||||
|
// sha224: {
|
||||||
|
// hash: sha224,
|
||||||
|
// tests: [secp224r1_sha224_test],
|
||||||
|
// },
|
||||||
|
sha256: {
|
||||||
|
hash: sha256,
|
||||||
|
tests: [secp224r1_sha256_test],
|
||||||
|
},
|
||||||
|
sha3_224: {
|
||||||
|
hash: sha3_224,
|
||||||
|
tests: [secp224r1_sha3_224_test],
|
||||||
|
},
|
||||||
|
sha3_256: {
|
||||||
|
hash: sha3_256,
|
||||||
|
tests: [secp224r1_sha3_256_test],
|
||||||
|
},
|
||||||
|
sha3_512: {
|
||||||
|
hash: sha3_512,
|
||||||
|
tests: [secp224r1_sha3_512_test],
|
||||||
|
},
|
||||||
|
sha512: {
|
||||||
|
hash: sha512,
|
||||||
|
tests: [secp224r1_sha512_test],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
secp256k1: {
|
||||||
|
curve: secp256k1,
|
||||||
|
hashes: {
|
||||||
|
// TODO: debug why fails, can be bug
|
||||||
|
sha256: {
|
||||||
|
hash: sha256,
|
||||||
|
tests: [secp256k1_sha256_test],
|
||||||
|
},
|
||||||
|
sha3_256: {
|
||||||
|
hash: sha3_256,
|
||||||
|
tests: [secp256k1_sha3_256_test],
|
||||||
|
},
|
||||||
|
sha3_512: {
|
||||||
|
hash: sha3_512,
|
||||||
|
tests: [secp256k1_sha3_512_test],
|
||||||
|
},
|
||||||
|
sha512: {
|
||||||
|
hash: sha512,
|
||||||
|
tests: [secp256k1_sha512_test],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
P256: {
|
||||||
|
curve: P256,
|
||||||
|
hashes: {
|
||||||
|
sha256: {
|
||||||
|
hash: sha256,
|
||||||
|
tests: [secp256r1_sha256_test],
|
||||||
|
},
|
||||||
|
sha3_256: {
|
||||||
|
hash: sha3_256,
|
||||||
|
tests: [secp256r1_sha3_256_test],
|
||||||
|
},
|
||||||
|
sha3_512: {
|
||||||
|
hash: sha3_512,
|
||||||
|
tests: [secp256r1_sha3_512_test],
|
||||||
|
},
|
||||||
|
sha512: {
|
||||||
|
hash: sha512,
|
||||||
|
tests: [secp256r1_sha512_test],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
P384: {
|
||||||
|
curve: P384,
|
||||||
|
hashes: {
|
||||||
|
sha384: {
|
||||||
|
hash: sha384,
|
||||||
|
tests: [secp384r1_sha384_test],
|
||||||
|
},
|
||||||
|
sha3_384: {
|
||||||
|
hash: sha3_384,
|
||||||
|
tests: [secp384r1_sha3_384_test],
|
||||||
|
},
|
||||||
|
sha3_512: {
|
||||||
|
hash: sha3_512,
|
||||||
|
tests: [secp384r1_sha3_512_test],
|
||||||
|
},
|
||||||
|
sha512: {
|
||||||
|
hash: sha512,
|
||||||
|
tests: [secp384r1_sha512_test],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
P521: {
|
||||||
|
curve: P521,
|
||||||
|
hashes: {
|
||||||
|
sha3_512: {
|
||||||
|
hash: sha3_512,
|
||||||
|
tests: [secp521r1_sha3_512_test],
|
||||||
|
},
|
||||||
|
sha512: {
|
||||||
|
hash: sha512,
|
||||||
|
tests: [secp521r1_sha512_test],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function runWycheproof(name, CURVE, group, index) {
|
||||||
|
const pubKey = CURVE.Point.fromHex(group.key.uncompressed);
|
||||||
|
deepStrictEqual(pubKey.x, BigInt(`0x${group.key.wx}`));
|
||||||
|
deepStrictEqual(pubKey.y, BigInt(`0x${group.key.wy}`));
|
||||||
|
for (const test of group.tests) {
|
||||||
|
const m = CURVE.CURVE.hash(hexToBytes(test.msg));
|
||||||
|
|
||||||
|
if (test.result === 'valid' || test.result === 'acceptable') {
|
||||||
|
try {
|
||||||
|
CURVE.Signature.fromDER(test.sig);
|
||||||
|
} catch (e) {
|
||||||
|
// Some tests has invalid signature which we don't accept
|
||||||
|
if (e.message.includes('Invalid signature: incorrect length')) continue;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
const verified = CURVE.verify(test.sig, m, pubKey);
|
||||||
|
if (name === 'secp256k1') {
|
||||||
|
// lowS: true for secp256k1
|
||||||
|
deepStrictEqual(verified, !CURVE.Signature.fromDER(test.sig).hasHighS(), `${index}: valid`);
|
||||||
|
} else {
|
||||||
|
deepStrictEqual(verified, true, `${index}: valid`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (test.result === 'invalid') {
|
||||||
|
let failed = false;
|
||||||
|
try {
|
||||||
|
failed = !CURVE.verify(test.sig, m, pubKey);
|
||||||
|
} catch (error) {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
deepStrictEqual(failed, true, `${index}: invalid`);
|
||||||
|
} else throw new Error('unknown test result');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const name in WYCHEPROOF_ECDSA) {
|
||||||
|
const { curve, hashes } = WYCHEPROOF_ECDSA[name];
|
||||||
|
for (const hName in hashes) {
|
||||||
|
const { hash, tests } = hashes[hName];
|
||||||
|
const CURVE = curve.create(hash);
|
||||||
|
should(`Wycheproof/WYCHEPROOF_ECDSA ${name}/${hName}`, () => {
|
||||||
|
for (let i = 0; i < tests.length; i++) {
|
||||||
|
const groups = tests[i].testGroups;
|
||||||
|
for (let j = 0; j < groups.length; j++) {
|
||||||
|
const group = groups[j];
|
||||||
|
runWycheproof(name, CURVE, group, `${i}/${j}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexToBigint = (hex) => BigInt(`0x${hex}`);
|
||||||
|
should('RFC6979', () => {
|
||||||
|
for (const v of rfc6979) {
|
||||||
|
const curve = NIST[v.curve];
|
||||||
|
deepStrictEqual(curve.CURVE.n, hexToBigint(v.q));
|
||||||
|
const pubKey = curve.getPublicKey(v.private);
|
||||||
|
const pubPoint = curve.Point.fromHex(pubKey);
|
||||||
|
deepStrictEqual(pubPoint.x, hexToBigint(v.Ux));
|
||||||
|
deepStrictEqual(pubPoint.y, hexToBigint(v.Uy));
|
||||||
|
for (const c of v.cases) {
|
||||||
|
const h = curve.CURVE.hash(c.message);
|
||||||
|
const sigObj = curve.sign(h, v.private);
|
||||||
|
deepStrictEqual(sigObj.r, hexToBigint(c.r), 'R');
|
||||||
|
deepStrictEqual(sigObj.s, hexToBigint(c.s), 'S');
|
||||||
|
deepStrictEqual(curve.verify(sigObj.toDERRawBytes(), h, pubKey), true, 'verify(1)');
|
||||||
|
deepStrictEqual(curve.verify(sigObj, h, pubKey), true, 'verify(2)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESM is broken.
|
||||||
|
import url from 'url';
|
||||||
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
||||||
|
should.run();
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { deepStrictEqual } from 'assert';
|
|
||||||
import { should } from 'micro-should';
|
|
||||||
import * as nist from '../lib/nist.js';
|
|
||||||
import { default as rfc6979 } from './fixtures/rfc6979.json' assert { type: 'json' };
|
|
||||||
function hexToBigint(hex) {
|
|
||||||
return BigInt('0x' + hex)
|
|
||||||
}
|
|
||||||
|
|
||||||
should('RFC6979', () => {
|
|
||||||
for (const v of rfc6979) {
|
|
||||||
const curve = nist[v.curve];
|
|
||||||
deepStrictEqual(curve.CURVE.n, hexToBigint(v.q));
|
|
||||||
const pubKey = curve.getPublicKey(v.private);
|
|
||||||
const pubPoint = curve.Point.fromHex(pubKey);
|
|
||||||
deepStrictEqual(pubPoint.x, hexToBigint(v.Ux));
|
|
||||||
deepStrictEqual(pubPoint.y, hexToBigint(v.Uy));
|
|
||||||
for (const c of v.cases) {
|
|
||||||
const h = curve.CURVE.hash(c.message);
|
|
||||||
const sigObj = curve.sign(h, v.private);
|
|
||||||
// const sigObj = curve.Signature.fromDER(sig);
|
|
||||||
deepStrictEqual(sigObj.r, hexToBigint(c.r), 'R');
|
|
||||||
deepStrictEqual(sigObj.s, hexToBigint(c.s), 'S');
|
|
||||||
deepStrictEqual(curve.verify(sigObj.toDERRawBytes(), h, pubKey), true, 'verify(1)');
|
|
||||||
deepStrictEqual(curve.verify(sigObj, h, pubKey), true, 'verify(2)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ESM is broken.
|
|
||||||
import url from 'url';
|
|
||||||
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
|
||||||
should.run();
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as fc from 'fast-check';
|
import * as fc from 'fast-check';
|
||||||
import * as nist from '../lib/nist.js';
|
import { secp256k1, schnorr } from '../lib/secp256k1.js';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' };
|
import { default as ecdsa } from './vectors/ecdsa.json' assert { type: 'json' };
|
||||||
import { default as ecdh } from './vectors/ecdh.json' assert { type: 'json' };
|
import { default as ecdh } from './vectors/ecdh.json' assert { type: 'json' };
|
||||||
@@ -7,11 +7,11 @@ import { default as privates } from './vectors/privates.json' assert { type: 'js
|
|||||||
import { default as points } from './vectors/points.json' assert { type: 'json' };
|
import { default as points } from './vectors/points.json' assert { type: 'json' };
|
||||||
import { default as wp } from './vectors/wychenproof.json' assert { type: 'json' };
|
import { default as wp } from './vectors/wychenproof.json' assert { type: 'json' };
|
||||||
import { should } from 'micro-should';
|
import { should } from 'micro-should';
|
||||||
import { deepStrictEqual, throws, rejects } from 'assert';
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
|
import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
|
||||||
|
|
||||||
const hex = bytesToHex;
|
const hex = bytesToHex;
|
||||||
const secp = nist.secp256k1;
|
const secp = secp256k1;
|
||||||
const privatesTxt = readFileSync('./test/vectors/privates-2.txt', 'utf-8');
|
const privatesTxt = readFileSync('./test/vectors/privates-2.txt', 'utf-8');
|
||||||
const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8');
|
const schCsv = readFileSync('./test/vectors/schnorr.csv', 'utf-8');
|
||||||
|
|
||||||
@@ -192,35 +192,28 @@ should('secp256k1.Signature.fromDERHex() roundtrip', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
should('secp256k1.sign()/should create deterministic signatures with RFC 6979', async () => {
|
should('secp256k1.sign()/should create deterministic signatures with RFC 6979', () => {
|
||||||
for (const vector of ecdsa.valid) {
|
for (const vector of ecdsa.valid) {
|
||||||
let usig = await secp.sign(vector.m, vector.d);
|
let usig = secp.sign(vector.m, vector.d);
|
||||||
let sig = (usig.toCompactHex());
|
let sig = usig.toCompactHex();
|
||||||
const vsig = vector.signature;
|
const vsig = vector.signature;
|
||||||
deepStrictEqual(sig.slice(0, 64), vsig.slice(0, 64));
|
deepStrictEqual(sig.slice(0, 64), vsig.slice(0, 64));
|
||||||
deepStrictEqual(sig.slice(64, 128), vsig.slice(64, 128));
|
deepStrictEqual(sig.slice(64, 128), vsig.slice(64, 128));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
should(
|
should('secp256k1.sign()/should not create invalid deterministic signatures with RFC 6979', () => {
|
||||||
'secp256k1.sign()/should not create invalid deterministic signatures with RFC 6979',
|
for (const vector of ecdsa.invalid.sign) {
|
||||||
async () => {
|
throws(() => secp.sign(vector.m, vector.d));
|
||||||
for (const vector of ecdsa.invalid.sign) {
|
|
||||||
throws(() => {
|
|
||||||
return secp.sign(vector.m, vector.d);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
should('secp256k1.sign()/edge cases', () => {
|
|
||||||
// @ts-ignore
|
|
||||||
rejects(async () => await secp.sign());
|
|
||||||
// @ts-ignore
|
|
||||||
rejects(async () => await secp.sign(''));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
should('secp256k1.sign()/should create correct DER encoding against libsecp256k1', async () => {
|
should('secp256k1.sign()/edge cases', () => {
|
||||||
|
throws(() => secp.sign());
|
||||||
|
throws(() => secp.sign(''));
|
||||||
|
});
|
||||||
|
|
||||||
|
should('secp256k1.sign()/should create correct DER encoding against libsecp256k1', () => {
|
||||||
const CASES = [
|
const CASES = [
|
||||||
[
|
[
|
||||||
'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b',
|
'd1a9dc8ed4e46a6a3e5e594615ca351d7d7ef44df1e4c94c1802f3592183794b',
|
||||||
@@ -236,14 +229,14 @@ should('secp256k1.sign()/should create correct DER encoding against libsecp256k1
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
const privKey = hexToBytes('0101010101010101010101010101010101010101010101010101010101010101');
|
const privKey = hexToBytes('0101010101010101010101010101010101010101010101010101010101010101');
|
||||||
for (let [msg, exp] of CASES) {
|
for (const [msg, exp] of CASES) {
|
||||||
const res = await secp.sign(msg, privKey, { extraEntropy: undefined });
|
const res = secp.sign(msg, privKey, { extraEntropy: undefined });
|
||||||
deepStrictEqual((res.toDERHex()), exp);
|
deepStrictEqual(res.toDERHex(), exp);
|
||||||
const rs = secp.Signature.fromDER(res.toDERHex()).toCompactHex();
|
const rs = secp.Signature.fromDER(res.toDERHex()).toCompactHex();
|
||||||
deepStrictEqual(secp.Signature.fromCompact(rs).toDERHex(), exp);
|
deepStrictEqual(secp.Signature.fromCompact(rs).toDERHex(), exp);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
should('secp256k1.sign()/sign ecdsa extraData', async () => {
|
should('secp256k1.sign()/sign ecdsa extraData', () => {
|
||||||
const ent1 = '0000000000000000000000000000000000000000000000000000000000000000';
|
const ent1 = '0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
const ent2 = '0000000000000000000000000000000000000000000000000000000000000001';
|
const ent2 = '0000000000000000000000000000000000000000000000000000000000000001';
|
||||||
const ent3 = '6e723d3fd94ed5d2b6bdd4f123364b0f3ca52af829988a63f8afe91d29db1c33';
|
const ent3 = '6e723d3fd94ed5d2b6bdd4f123364b0f3ca52af829988a63f8afe91d29db1c33';
|
||||||
@@ -251,56 +244,52 @@ should('secp256k1.sign()/sign ecdsa extraData', async () => {
|
|||||||
const ent5 = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
|
const ent5 = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
|
||||||
|
|
||||||
for (const e of ecdsa.extraEntropy) {
|
for (const e of ecdsa.extraEntropy) {
|
||||||
const sign = async (extraEntropy) => {
|
const sign = (extraEntropy) => {
|
||||||
const s = secp.sign(e.m, e.d, {extraEntropy }).toCompactHex();
|
const s = secp.sign(e.m, e.d, { extraEntropy }).toCompactHex();
|
||||||
return s;
|
return s;
|
||||||
};
|
};
|
||||||
deepStrictEqual(await sign(), e.signature);
|
deepStrictEqual(sign(), e.signature);
|
||||||
deepStrictEqual(await sign(ent1), e.extraEntropy0);
|
deepStrictEqual(sign(ent1), e.extraEntropy0);
|
||||||
deepStrictEqual(await sign(ent2), e.extraEntropy1);
|
deepStrictEqual(sign(ent2), e.extraEntropy1);
|
||||||
deepStrictEqual(await sign(ent3), e.extraEntropyRand);
|
deepStrictEqual(sign(ent3), e.extraEntropyRand);
|
||||||
deepStrictEqual(await sign(ent4), e.extraEntropyN);
|
deepStrictEqual(sign(ent4), e.extraEntropyN);
|
||||||
deepStrictEqual(await sign(ent5), e.extraEntropyMax);
|
deepStrictEqual(sign(ent5), e.extraEntropyMax);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
should('secp256k1.verify()/should verify signature', async () => {
|
should('secp256k1.verify()/should verify signature', () => {
|
||||||
const MSG = '01'.repeat(32);
|
const MSG = '01'.repeat(32);
|
||||||
const PRIV_KEY = 0x2n;
|
const PRIV_KEY = 0x2n;
|
||||||
const signature = await secp.sign(MSG, PRIV_KEY);
|
const signature = secp.sign(MSG, PRIV_KEY);
|
||||||
const publicKey = secp.getPublicKey(PRIV_KEY);
|
const publicKey = secp.getPublicKey(PRIV_KEY);
|
||||||
deepStrictEqual(publicKey.length, 65);
|
deepStrictEqual(publicKey.length, 65);
|
||||||
deepStrictEqual(secp.verify(signature, MSG, publicKey), true);
|
deepStrictEqual(secp.verify(signature, MSG, publicKey), true);
|
||||||
});
|
});
|
||||||
should('secp256k1.verify()/should not verify signature with wrong public key', async () => {
|
should('secp256k1.verify()/should not verify signature with wrong public key', () => {
|
||||||
const MSG = '01'.repeat(32);
|
const MSG = '01'.repeat(32);
|
||||||
const PRIV_KEY = 0x2n;
|
const PRIV_KEY = 0x2n;
|
||||||
const WRONG_PRIV_KEY = 0x22n;
|
const WRONG_PRIV_KEY = 0x22n;
|
||||||
const signature = await secp.sign(MSG, PRIV_KEY);
|
const signature = secp.sign(MSG, PRIV_KEY);
|
||||||
const publicKey = secp.Point.fromPrivateKey(WRONG_PRIV_KEY).toHex();
|
const publicKey = secp.Point.fromPrivateKey(WRONG_PRIV_KEY).toHex();
|
||||||
deepStrictEqual(publicKey.length, 130);
|
deepStrictEqual(publicKey.length, 130);
|
||||||
deepStrictEqual(secp.verify(signature, MSG, publicKey), false);
|
deepStrictEqual(secp.verify(signature, MSG, publicKey), false);
|
||||||
});
|
});
|
||||||
should('secp256k1.verify()/should not verify signature with wrong hash', async () => {
|
should('secp256k1.verify()/should not verify signature with wrong hash', () => {
|
||||||
const MSG = '01'.repeat(32);
|
const MSG = '01'.repeat(32);
|
||||||
const PRIV_KEY = 0x2n;
|
const PRIV_KEY = 0x2n;
|
||||||
const WRONG_MSG = '11'.repeat(32);
|
const WRONG_MSG = '11'.repeat(32);
|
||||||
const signature = await secp.sign(MSG, PRIV_KEY);
|
const signature = secp.sign(MSG, PRIV_KEY);
|
||||||
const publicKey = secp.getPublicKey(PRIV_KEY);
|
const publicKey = secp.getPublicKey(PRIV_KEY);
|
||||||
deepStrictEqual(publicKey.length, 65);
|
deepStrictEqual(publicKey.length, 65);
|
||||||
deepStrictEqual(secp.verify(signature, WRONG_MSG, publicKey), false);
|
deepStrictEqual(secp.verify(signature, WRONG_MSG, publicKey), false);
|
||||||
});
|
});
|
||||||
should('secp256k1.verify()/should verify random signatures', async () =>
|
should('secp256k1.verify()/should verify random signatures', () =>
|
||||||
fc.assert(
|
fc.assert(
|
||||||
fc.asyncProperty(
|
fc.property(FC_BIGINT, fc.hexaString({ minLength: 64, maxLength: 64 }), (privKey, msg) => {
|
||||||
FC_BIGINT,
|
const pub = secp.getPublicKey(privKey);
|
||||||
fc.hexaString({ minLength: 64, maxLength: 64 }),
|
const sig = secp.sign(msg, privKey);
|
||||||
async (privKey, msg) => {
|
deepStrictEqual(secp.verify(sig, msg, pub), true);
|
||||||
const pub = secp.getPublicKey(privKey);
|
})
|
||||||
const sig = await secp.sign(msg, privKey);
|
|
||||||
deepStrictEqual(secp.verify(sig, msg, pub), true);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
should('secp256k1.verify()/should not verify signature with invalid r/s', () => {
|
should('secp256k1.verify()/should not verify signature with invalid r/s', () => {
|
||||||
@@ -315,16 +304,14 @@ should('secp256k1.verify()/should not verify signature with invalid r/s', () =>
|
|||||||
|
|
||||||
const pub = new secp.Point(x, y);
|
const pub = new secp.Point(x, y);
|
||||||
const signature = new secp.Signature(2n, 2n);
|
const signature = new secp.Signature(2n, 2n);
|
||||||
// @ts-ignore
|
|
||||||
signature.r = r;
|
signature.r = r;
|
||||||
// @ts-ignore
|
|
||||||
signature.s = s;
|
signature.s = s;
|
||||||
|
|
||||||
const verified = secp.verify(signature, msg, pub);
|
const verified = secp.verify(signature, msg, pub);
|
||||||
// Verifies, but it shouldn't, because signature S > curve order
|
// Verifies, but it shouldn't, because signature S > curve order
|
||||||
deepStrictEqual(verified, false);
|
deepStrictEqual(verified, false);
|
||||||
});
|
});
|
||||||
should('secp256k1.verify()/should not verify msg = curve order', async () => {
|
should('secp256k1.verify()/should not verify msg = curve order', () => {
|
||||||
const msg = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141';
|
const msg = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141';
|
||||||
const x = 55066263022277343669578718895168534326250603453777594175500187360389116729240n;
|
const x = 55066263022277343669578718895168534326250603453777594175500187360389116729240n;
|
||||||
const y = 32670510020758816978083085130507043184471273380659243275938904335757337482424n;
|
const y = 32670510020758816978083085130507043184471273380659243275938904335757337482424n;
|
||||||
@@ -334,7 +321,7 @@ should('secp256k1.verify()/should not verify msg = curve order', async () => {
|
|||||||
const sig = new secp.Signature(r, s);
|
const sig = new secp.Signature(r, s);
|
||||||
deepStrictEqual(secp.verify(sig, msg, pub), false);
|
deepStrictEqual(secp.verify(sig, msg, pub), false);
|
||||||
});
|
});
|
||||||
should('secp256k1.verify()/should verify non-strict msg bb5a...', async () => {
|
should('secp256k1.verify()/should verify non-strict msg bb5a...', () => {
|
||||||
const msg = 'bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023';
|
const msg = 'bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023';
|
||||||
const x = 3252872872578928810725465493269682203671229454553002637820453004368632726370n;
|
const x = 3252872872578928810725465493269682203671229454553002637820453004368632726370n;
|
||||||
const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n;
|
const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n;
|
||||||
@@ -354,47 +341,35 @@ should(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// describe('schnorr', () => {
|
// index,secret key,public key,aux_rand,message,signature,verification result,comment
|
||||||
// // index,secret key,public key,aux_rand,message,signature,verification result,comment
|
const vectors = schCsv
|
||||||
// const vectors = schCsv
|
.split('\n')
|
||||||
// .split('\n')
|
.map((line) => line.split(','))
|
||||||
// .map((line: string) => line.split(','))
|
.slice(1, -1);
|
||||||
// .slice(1, -1);
|
for (let vec of vectors) {
|
||||||
// for (let vec of vectors) {
|
const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec;
|
||||||
// const [index, sec, pub, rnd, msg, expSig, passes, comment] = vec;
|
should(`sign with Schnorr scheme vector ${index}`, () => {
|
||||||
// it(`should sign with Schnorr scheme vector ${index}`, async () => {
|
if (sec) {
|
||||||
// if (sec) {
|
deepStrictEqual(hex(schnorr.getPublicKey(sec)), pub.toLowerCase());
|
||||||
// expect(hex(secp.schnorr.getPublicKey(sec))).toBe(pub.toLowerCase());
|
const sig = schnorr.sign(msg, sec, rnd);
|
||||||
// const sig = await secp.schnorr.sign(msg, sec, rnd);
|
deepStrictEqual(hex(sig), expSig.toLowerCase());
|
||||||
// const sigS = secp.schnorr.signSync(msg, sec, rnd);
|
deepStrictEqual(schnorr.verify(sig, msg, pub), true);
|
||||||
// expect(hex(sig)).toBe(expSig.toLowerCase());
|
} else {
|
||||||
// expect(hex(sigS)).toBe(expSig.toLowerCase());
|
const passed = schnorr.verify(expSig, msg, pub);
|
||||||
// expect(await secp.schnorr.verify(sigS, msg, pub)).toBe(true);
|
deepStrictEqual(passed, passes === 'TRUE');
|
||||||
// expect(secp.schnorr.verifySync(sig, msg, pub)).toBe(true);
|
}
|
||||||
// } else {
|
});
|
||||||
// const passed = await secp.schnorr.verify(expSig, msg, pub);
|
}
|
||||||
// const passedS = secp.schnorr.verifySync(expSig, msg, pub);
|
|
||||||
// if (passes === 'TRUE') {
|
|
||||||
// expect(passed).toBeTruthy();
|
|
||||||
// expect(passedS).toBeTruthy();
|
|
||||||
// } else {
|
|
||||||
// expect(passed).toBeFalsy();
|
|
||||||
// expect(passedS).toBeFalsy();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
should('secp256k1.recoverPublicKey()/should recover public key from recovery bit', async () => {
|
should('secp256k1.recoverPublicKey()/should recover public key from recovery bit', () => {
|
||||||
const message = '00000000000000000000000000000000000000000000000000000000deadbeef';
|
const message = '00000000000000000000000000000000000000000000000000000000deadbeef';
|
||||||
const privateKey = 123456789n;
|
const privateKey = 123456789n;
|
||||||
const publicKey = secp.Point.fromHex(secp.getPublicKey(privateKey)).toHex(false);
|
const publicKey = secp.Point.fromHex(secp.getPublicKey(privateKey)).toHex(false);
|
||||||
const sig = await secp.sign(message, privateKey);
|
const sig = secp.sign(message, privateKey);
|
||||||
const recoveredPubkey = sig.recoverPublicKey(message);
|
const recoveredPubkey = sig.recoverPublicKey(message);
|
||||||
// const recoveredPubkey = secp.recoverPublicKey(message, signature, recovery);
|
// const recoveredPubkey = secp.recoverPublicKey(message, signature, recovery);
|
||||||
deepStrictEqual(recoveredPubkey !== null, true);
|
deepStrictEqual(recoveredPubkey !== null, true);
|
||||||
deepStrictEqual((recoveredPubkey).toHex(), publicKey);
|
deepStrictEqual(recoveredPubkey.toHex(), publicKey);
|
||||||
deepStrictEqual(secp.verify(sig, message, publicKey), true);
|
deepStrictEqual(secp.verify(sig, message, publicKey), true);
|
||||||
});
|
});
|
||||||
should('secp256k1.recoverPublicKey()/should not recover zero points', () => {
|
should('secp256k1.recoverPublicKey()/should not recover zero points', () => {
|
||||||
@@ -404,22 +379,22 @@ should('secp256k1.recoverPublicKey()/should not recover zero points', () => {
|
|||||||
const recovery = 0;
|
const recovery = 0;
|
||||||
throws(() => secp.recoverPublicKey(msgHash, sig, recovery));
|
throws(() => secp.recoverPublicKey(msgHash, sig, recovery));
|
||||||
});
|
});
|
||||||
should('secp256k1.recoverPublicKey()/should handle all-zeros msghash', async () => {
|
should('secp256k1.recoverPublicKey()/should handle all-zeros msghash', () => {
|
||||||
const privKey = secp.utils.randomPrivateKey();
|
const privKey = secp.utils.randomPrivateKey();
|
||||||
const pub = secp.getPublicKey(privKey);
|
const pub = secp.getPublicKey(privKey);
|
||||||
const zeros = '0000000000000000000000000000000000000000000000000000000000000000';
|
const zeros = '0000000000000000000000000000000000000000000000000000000000000000';
|
||||||
const sig = await secp.sign(zeros, privKey, { recovered: true });
|
const sig = secp.sign(zeros, privKey, { recovered: true });
|
||||||
const recoveredKey = sig.recoverPublicKey(zeros);
|
const recoveredKey = sig.recoverPublicKey(zeros);
|
||||||
deepStrictEqual(recoveredKey.toRawBytes(), pub);
|
deepStrictEqual(recoveredKey.toRawBytes(), pub);
|
||||||
});
|
});
|
||||||
should('secp256k1.recoverPublicKey()/should handle RFC 6979 vectors', async () => {
|
should('secp256k1.recoverPublicKey()/should handle RFC 6979 vectors', () => {
|
||||||
for (const vector of ecdsa.valid) {
|
for (const vector of ecdsa.valid) {
|
||||||
if (secp.utils.mod(hexToNumber(vector.m), secp.CURVE.n) === 0n) continue;
|
if (secp.utils.mod(hexToNumber(vector.m), secp.CURVE.n) === 0n) continue;
|
||||||
let usig = secp.sign(vector.m, vector.d);
|
let usig = secp.sign(vector.m, vector.d);
|
||||||
let sig = (usig).toDERHex();
|
let sig = usig.toDERHex();
|
||||||
const vpub = secp.getPublicKey(vector.d);
|
const vpub = secp.getPublicKey(vector.d);
|
||||||
const recovered = usig.recoverPublicKey(vector.m);
|
const recovered = usig.recoverPublicKey(vector.m);
|
||||||
deepStrictEqual((recovered).toHex(), hex(vpub));
|
deepStrictEqual(recovered.toHex(), hex(vpub));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { deepStrictEqual, throws } from 'assert';
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
import { should } from 'micro-should';
|
import { should } from 'micro-should';
|
||||||
import * as starknet from '../../lib/starknet.js';
|
import * as starknet from '../../lib/stark.js';
|
||||||
import { default as issue2 } from './fixtures/issue2.json' assert { type: 'json' };
|
import { default as issue2 } from './fixtures/issue2.json' assert { type: 'json' };
|
||||||
|
|
||||||
should('Basic elliptic sanity check', () => {
|
should('Basic elliptic sanity check', () => {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as microStark from '../../../lib/starknet.js';
|
import * as microStark from '../../../lib/stark.js';
|
||||||
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
|
import * as starkwareCrypto from '@starkware-industries/starkware-crypto-utils';
|
||||||
import * as bench from 'micro-bmark';
|
import * as bench from 'micro-bmark';
|
||||||
const { run, mark } = bench; // or bench.mark
|
const { run, mark } = bench; // or bench.mark
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import './basic.test.js';
|
import './basic.test.js';
|
||||||
import './starknet.test.js';
|
import './stark.test.js';
|
||||||
import './property.test.js';
|
import './property.test.js';
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { deepStrictEqual, throws } from 'assert';
|
import { deepStrictEqual, throws } from 'assert';
|
||||||
import { should } from 'micro-should';
|
import { should } from 'micro-should';
|
||||||
import * as starknet from '../../lib/starknet.js';
|
import * as starknet from '../../lib/stark.js';
|
||||||
import * as fc from 'fast-check';
|
import * as fc from 'fast-check';
|
||||||
|
|
||||||
const FC_BIGINT = fc.bigInt(1n + 1n, starknet.CURVE.n - 1n);
|
const FC_BIGINT = fc.bigInt(1n + 1n, starknet.CURVE.n - 1n);
|
||||||
@@ -3,7 +3,7 @@ import { should } from 'micro-should';
|
|||||||
import { hex, utf8 } from '@scure/base';
|
import { hex, utf8 } from '@scure/base';
|
||||||
import * as bip32 from '@scure/bip32';
|
import * as bip32 from '@scure/bip32';
|
||||||
import * as bip39 from '@scure/bip39';
|
import * as bip39 from '@scure/bip39';
|
||||||
import * as starknet from '../../lib/starknet.js';
|
import * as starknet from '../../lib/stark.js';
|
||||||
import { default as sigVec } from './fixtures/rfc6979_signature_test_vector.json' assert { type: 'json' };
|
import { default as sigVec } from './fixtures/rfc6979_signature_test_vector.json' assert { type: 'json' };
|
||||||
import { default as precomputedKeys } from './fixtures/keys_precomputed.json' assert { type: 'json' };
|
import { default as precomputedKeys } from './fixtures/keys_precomputed.json' assert { type: 'json' };
|
||||||
|
|
||||||
4218
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha224_test.json
Normal file
4218
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha224_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4447
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha256_test.json
Normal file
4447
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha256_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4444
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha3_224_test.json
Normal file
4444
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha3_224_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4516
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha3_256_test.json
Normal file
4516
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha3_256_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5036
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha3_512_test.json
Normal file
5036
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha3_512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5002
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha512_test.json
Normal file
5002
curve-definitions/test/wycheproof/ecdsa_secp224r1_sha512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4474
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha256_test.json
Normal file
4474
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha256_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4538
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha3_256_test.json
Normal file
4538
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha3_256_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5066
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha3_512_test.json
Normal file
5066
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha3_512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5034
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha512_test.json
Normal file
5034
curve-definitions/test/wycheproof/ecdsa_secp256k1_sha512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4578
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha256_test.json
Normal file
4578
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha256_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4642
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha3_256_test.json
Normal file
4642
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha3_256_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5172
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha3_512_test.json
Normal file
5172
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha3_512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5138
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha512_test.json
Normal file
5138
curve-definitions/test/wycheproof/ecdsa_secp256r1_sha512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4634
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha384_test.json
Normal file
4634
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha384_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4711
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha3_384_test.json
Normal file
4711
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha3_384_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4972
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha3_512_test.json
Normal file
4972
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha3_512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4940
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha512_test.json
Normal file
4940
curve-definitions/test/wycheproof/ecdsa_secp384r1_sha512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5005
curve-definitions/test/wycheproof/ecdsa_secp521r1_sha3_512_test.json
Normal file
5005
curve-definitions/test/wycheproof/ecdsa_secp521r1_sha3_512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
4989
curve-definitions/test/wycheproof/ecdsa_secp521r1_sha512_test.json
Normal file
4989
curve-definitions/test/wycheproof/ecdsa_secp521r1_sha512_test.json
Normal file
File diff suppressed because it is too large
Load Diff
908
curve-definitions/test/wycheproof/ed448_test.json
Normal file
908
curve-definitions/test/wycheproof/ed448_test.json
Normal file
@@ -0,0 +1,908 @@
|
|||||||
|
{
|
||||||
|
"algorithm" : "EDDSA",
|
||||||
|
"generatorVersion" : "0.8r12",
|
||||||
|
"numberOfTests" : 86,
|
||||||
|
"header" : [
|
||||||
|
"Test vectors of type EddsaVerify are intended for testing",
|
||||||
|
"the verification of Eddsa signatures."
|
||||||
|
],
|
||||||
|
"notes" : {
|
||||||
|
"SignatureMalleability" : "EdDSA signatures are non-malleable, if implemented accordingly. Failing to check the range of S allows to modify signatures. See RFC 8032, Section 5.2.7 and Section 8.4."
|
||||||
|
},
|
||||||
|
"schema" : "eddsa_verify_schema.json",
|
||||||
|
"testGroups" : [
|
||||||
|
{
|
||||||
|
"jwk" : {
|
||||||
|
"crv" : "Ed448",
|
||||||
|
"d" : "iDAeB2UY01N_kwLuD1Ij5LY-HwFgB9PC69_sX3CZfoEZxrrQrnuAP0h5HKjsVJqiobhi96UVkLnV",
|
||||||
|
"kid" : "none",
|
||||||
|
"kty" : "OKP",
|
||||||
|
"x" : "QZYQpTSvEn9YOwSBjNt_D_MAsCXy4BaCvK4z_Wkc7gOVEd8M3caQ7peEJuizjlDOWvfc-6UPcEwA"
|
||||||
|
},
|
||||||
|
"key" : {
|
||||||
|
"curve" : "edwards448",
|
||||||
|
"keySize" : 448,
|
||||||
|
"pk" : "419610a534af127f583b04818cdb7f0ff300b025f2e01682bcae33fd691cee039511df0cddc690ee978426e8b38e50ce5af7dcfba50f704c00",
|
||||||
|
"sk" : "88301e076518d3537f9302ee0f5223e4b63e1f016007d3c2ebdfec5f70997e8119c6bad0ae7b803f48791ca8ec549aa2a1b862f7a51590b9d5",
|
||||||
|
"type" : "EDDSAKeyPair"
|
||||||
|
},
|
||||||
|
"keyDer" : "3043300506032b6571033a00419610a534af127f583b04818cdb7f0ff300b025f2e01682bcae33fd691cee039511df0cddc690ee978426e8b38e50ce5af7dcfba50f704c00",
|
||||||
|
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAQZYQpTSvEn9YOwSBjNt/D/MAsCXy4BaCvK4z/Wkc7gOVEd8M3caQ7peEJuizjlDOWvfc+6UPcEwA\n-----END PUBLIC KEY-----\n",
|
||||||
|
"type" : "EddsaVerify",
|
||||||
|
"tests" : [
|
||||||
|
{
|
||||||
|
"tcId" : 1,
|
||||||
|
"comment" : "",
|
||||||
|
"msg" : "",
|
||||||
|
"sig" : "cf7953007666e12f73af9ec92e3e018da5ee5a8d5b17f5100a354c58f1d5f4bb37ab835c52f72374c72d612689149cf6d36a70db6dc5a6c400b597348e0e31e51e65bb144e63c892a367b4c055c036aa6cd7e728cdd2a098963bda863903e6dd025b5a5d891209f4e28537694804e50b0800",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 2,
|
||||||
|
"comment" : "",
|
||||||
|
"msg" : "78",
|
||||||
|
"sig" : "c56e94d5c9ca860c244f33db556bf6b3cec38b024b77604a35d6a07211b1316b9a027133c374b86f72665cc45ce01583a2e0f2775c6172da801acef168717cab1196cddfb149359dfef589756257cc2d6b02fc516d8d41b4adaa3f11428f41410ef0dc3c1b008d3d052173d4389508ed0100",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 3,
|
||||||
|
"comment" : "",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd982600",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 4,
|
||||||
|
"comment" : "",
|
||||||
|
"msg" : "48656c6c6f",
|
||||||
|
"sig" : "442e33780f199dd7bc71d1335f74df7f3a0ec789e21a175c1bffddb6e50091998d969ac8194b3acefb7702f6c222f84f7eeca3b80406f1fe80687915e7925bf52deb47b6b779e26d30eec7c5fef03580f280a089eefd0bacc9fbbb6a4d73a591d1671d192e6bbcfdb79ad3db5673a1263000",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 5,
|
||||||
|
"comment" : "",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff28060a05236fc9c1682b0e55b60a082c9a57bffe61ef4dda5ce65df539805122b3a09a05976d41ad68ab52df85428152c57da93531e5d16920e00",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 6,
|
||||||
|
"comment" : "",
|
||||||
|
"msg" : "000000000000000000000000",
|
||||||
|
"sig" : "a8ca64d1ab00eae77fd2854d8422db3ae12fca91c14f274f30a44df98590786ec4cbb96a9564fc1b9b16c22d2bd00aa65f0876323729f5ac809fb0b89a4d3f27afbabb596851d835173d60ea34e0875359f3d6adb13cef1395b7eaa5f9147583ff38b4deb183062874915bf194ae61072300",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 7,
|
||||||
|
"comment" : "",
|
||||||
|
"msg" : "6161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161",
|
||||||
|
"sig" : "b205d3e24ccef64c1e86f15f48ddfa682453503489475188b04a8f55860b3c8a9c01e6de820bb7d9b15daff8de25a4a870e987157a115ec1802da0d0606da12842ea7eab658b5eea6dd1f3a641a5174425578003cd318b8d6b8dcb4de954b5078d1912c578ad8281515d6df3672b94173f00",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 8,
|
||||||
|
"comment" : "",
|
||||||
|
"msg" : "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60",
|
||||||
|
"sig" : "3492ef66e5fdf1503e9e206c5c2f0d4b7891aad793575527d2251e0df1b97c2feac188bc382ce3c92c4bc36ba2695f32bedadd480eaa932300d0db1f9a9c60844d2ea5aea64933c7be46c4f9d21cb48b39eae23d08496de7ce9501197185cc5d4ff8aa4b018ce7ad321f6a7d778c4a070400",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 9,
|
||||||
|
"comment" : "",
|
||||||
|
"msg" : "ffffffffffffffffffffffffffffffff",
|
||||||
|
"sig" : "545e1905af1b5886552eaf78e17304c6f83fcfb3444df2d1ea056486db615e3bb29131bb0c1fd295364dc515dae581967148eb23c6c9012e806d3623baff00548c648e3cb3756aaaaf659f2fb7dd2e71c7611448593ca63f2a98913ab7f182e6820eaf1334e2745e0e7bc0dccab98de71600",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 10,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 11,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 12,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f24458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 13,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 14,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 15,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 16,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 17,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f24458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 18,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 19,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 20,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "f34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 21,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "f34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 22,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "f34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3ff24458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 23,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "f34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3ff34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 24,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "f34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 25,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 26,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 27,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffff24458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 28,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffff34458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 29,
|
||||||
|
"comment" : "special values for r and s",
|
||||||
|
"msg" : "3f",
|
||||||
|
"sig" : "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 30,
|
||||||
|
"comment" : "empty signature",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 31,
|
||||||
|
"comment" : "s missing",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f280",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 32,
|
||||||
|
"comment" : "signature too short",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd98",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 33,
|
||||||
|
"comment" : "signature too long",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd9826002020",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 34,
|
||||||
|
"comment" : "include pk in signature",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd982600419610a534af127f583b04818cdb7f0ff300b025f2e01682bcae33fd691cee039511df0cddc690ee978426e8b38e50ce5af7dcfba50f704c00",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 35,
|
||||||
|
"comment" : "prepending 0 byte to signature",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "005d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd982600",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 36,
|
||||||
|
"comment" : "prepending 0 byte to s",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f2800031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd982600",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 37,
|
||||||
|
"comment" : "appending 0 byte to signature",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd98260000",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 38,
|
||||||
|
"comment" : "removing 0 byte from signature",
|
||||||
|
"msg" : "5465737430",
|
||||||
|
"sig" : "dbd6384516ab6b0eb2d609414564ec217383b66040dfb0676128251ae24c1d7c179c21a9ee307dc13f8fe6550bc40187f093da85617bcf5d009d3ee8b798ad978b6e683bc4e911940ea82ea0b7e95dc24fe0b29e44663211892c2aaa3451379d22c289b94378f11fb700f1689d4a00d73e",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 39,
|
||||||
|
"comment" : "removing 0 byte from signature",
|
||||||
|
"msg" : "546573743535",
|
||||||
|
"sig" : "ce2b2fff0bf445a36813cf2a76e0cc5619a4f16ee53f0fe3cd46fc0414db7248b32fbda54bbb37e708d6238076ea12bf850b964b044520bb80fbaf0e1d1ed3bcab261462df5e7f2de73ac9cbae26dfa29015039acf90575961fc9b91b9ca276dae7d5fa805bd202c5579a0f4c66e801400",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 40,
|
||||||
|
"comment" : "dropping byte from signature",
|
||||||
|
"msg" : "546573743633",
|
||||||
|
"sig" : "c283ed36d78c275a5d02f7939aed2c4ef68320ae1bf6fc25e834b758046a6d52a480216a942dfe771f3bd307f4ce7d3f446e0824961bd5de80cda42b5cc38e6ec3d53f386978b9877d3c98a28ac8fc66630ffd178933a18de1aee23cab5011c9ff4c9277311b4c6c33acb8e82b8c693c00",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 41,
|
||||||
|
"comment" : "removing leading 0 byte from signature",
|
||||||
|
"msg" : "54657374333631",
|
||||||
|
"sig" : "62e629bd2b8f595df401c362c766216d45de89fceecd99c69d323b5c53ad5ac3ea7224963feba2f2895551d94f548248ef8597d2a959f880d59934a5e8f07847834d66ba1a6b09de5dba692172b13f768f0c29e8196144c130d2353445d63cbd0b690794fdad30a48e8bb7cc2504f80700",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 42,
|
||||||
|
"comment" : "modified bit 0 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5cb94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280afc33a525116cc12e0d1c3a1fde6de518a6544f360d0fe18d5be7770b057a2bf792db4b7648fa84a6eaecae909e33fa59c5dfe4804ba2623",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 43,
|
||||||
|
"comment" : "modified bit 1 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5fb94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280f91386c3e9dd9e7c9af7ca6bbef8b7a44ae3d68eeade449d7dfbb31de8419eb943e2ecbcdd06df5227e82b9ded519a56e70f0a1c0fc17b06",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 44,
|
||||||
|
"comment" : "modified bit 2 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "59b94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280f1aab07b4ad069dfafc01b4532e1e44cbf7177e1bdda197fc87434046db5b935afd9114ac5e1138eaead23c3b59dba9026d2da4a86fe800b",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 45,
|
||||||
|
"comment" : "modified bit 7 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "ddb94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2807668402b7b093fc754019324077c1f842a7d2e35adf7b87094115cec459ad5419e162988ef42b1988d9b944d9d5a7ce09c6f342afa500839",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 46,
|
||||||
|
"comment" : "modified bit 8 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db84c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280279b70338586b9e13e669191cc0dfc2a937d50a6118758de04a4ca41f4877abdb971afa87fe4b83bc243b8dfd2cb368aa389a4cb11e83e31",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 47,
|
||||||
|
"comment" : "modified bit 16 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94d53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280c7b847556b3a6f9447483899ab730a23004c695054dd57b1c3214fa87f632f39c8ff1471f0532b8eee4154930e1ca30d574b8f9e85b0432b",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 48,
|
||||||
|
"comment" : "modified bit 31 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94cd3101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2800b017917472b130a1cc1c8e995a252617d5ddaf1f3d48930b4876fa0d2cfedec90a8c85c8274892a1ca3b6cfce63ebfebc307210b844ae0c",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 49,
|
||||||
|
"comment" : "modified bit 32 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53111f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2805f38f6371860fcc4f2ec515afd35cb05d8941e2448cc469a15b8537e758b16d46b123581613462c2bb20d8a07299ab795d0998e1e4277931",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 50,
|
||||||
|
"comment" : "modified bit 63 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f529f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff28017111ba6fefd45e2490f1d53a184007fa073470706d7f4a9606fcad2954e74c32116ba7701d225b76e55164e64df3245c1031f0df734bd31",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 51,
|
||||||
|
"comment" : "modified bit 64 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6d1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2808d7d0aa1fd81d0e31789921771c654338f96f0b557b615e3da55670271608a0e022e4e8cf393e309f8f6412281b6147e7fce42b089eb1e0c",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 52,
|
||||||
|
"comment" : "modified bit 97 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ca4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280b08d3be6ebf4e60bf6d74e105ea2fa9b965c62816bbd22ea3bb0c1acfd12300523ca76f94b6f789488a957fbeb212d713baccf95fd594f3d",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 53,
|
||||||
|
"comment" : "modified bit 127 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7606fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280a23f54857e9b0f72b2ef90d2768834590464d75933ed08c454faa762b3702a2b631c33c339d05b2e24c20a8214f99af31f93f80f416a1129",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 54,
|
||||||
|
"comment" : "modified bit 240 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0881a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280734bdc399273d3403d934ceaae16e87a68c6bff6b77d8037ff41c97922498a58e704c29ab519d41bab70735f71fc26f589361e2b21754300",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 55,
|
||||||
|
"comment" : "modified bit 247 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0800a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280ba961cc8d0765c99d57470ee1c0c77f0a562a198fd0175eddb0c033e0fb8525328c5e2c516e2b00f73609c7f769195eb1a02ff54090d781f",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 56,
|
||||||
|
"comment" : "modified bit 248 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a97b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280e72685907da9e5a64e4142ed02fc0c6bf95763201db5942aac055fa87e6fdd32e483fd21ed4110d5d7ef619b740fef2ad8a71fe821e42a2a",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 57,
|
||||||
|
"comment" : "modified bit 253 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880887b8e55858df4cf2291a7303ffda446b82a117b4dd408cff280500646d67c74f13471f0ad034da530f7238fe7897e532af8ec2977643a410b1d054934df567e170276389e66b3f3ccb3c15aed239d04f72b",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 58,
|
||||||
|
"comment" : "modified bit 254 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880e87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2807bb153b8e350aa736a91c921217578539600c1299ab76522ef8f6902d79c93f274073ee6beafe6200ecaf59f7cd11bb1c833f24bf30ed52d",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 59,
|
||||||
|
"comment" : "modified bit 255 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880287b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2804a67b22be599d6433b87ea961c82c457ab50f64ac6b7efb0b2f90988927f83742303c278f8248e02d5679b41ed505aba0fb51110d0def810",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 60,
|
||||||
|
"comment" : "modified bit 440 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff3807f452efb0cd97dab5506028b7b876830dee02a9c0cbd140dcde509638d4d546c30856b2151bdf79930df5bbb11f2beb66bcdc25ad75f2116",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 61,
|
||||||
|
"comment" : "modified bit 441 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff0808d78231bb3c9a87c5b8d168fe05f8197503a3d73a6d700f436b5a76ab866388baa6930191a077aca7970058932c88b7f9e6ecb13c89dcd1d",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 62,
|
||||||
|
"comment" : "modified bit 447 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cf72809e5a8406063fb3545f0fb627f841b2e3a85ad5d378018e8b58fe58e14ee5520d57abc9140e9c5a75a8b09ac3334dd0cad69b48771284321d",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 63,
|
||||||
|
"comment" : "modified bit 448 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2811adf92201088e051ee48b57aecf46edfc68e5baeed5ae4910ba5681d370f75ab593811e18293ef0808581c254196bcbf2b4c454136a6711b",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 64,
|
||||||
|
"comment" : "modified bit 449 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2825e06c3999e8308be439c40940b0075d3e4f65147c1608cbe6e9c432e33bed6686f9393ae2568f0ad60febcb4b6179c0d90d034e7c3c46810",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 65,
|
||||||
|
"comment" : "modified bit 454 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2c02456bbd141df048dbf1843be6d5fef402483314c2af547b361a09f3319489eaede43404df9faf634c1298d678b5261c808b0be3726013e39",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 66,
|
||||||
|
"comment" : "modified bit 455 in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "5db94c53101f521f6c1f43b60ea4d7e06fbd49c2e8afaf4fcc289e645e0880a87b8e55858df4cf2291a7303ffda446b82a117b4dd408cff2007106d2a896a7fec6dee53eea272d9b6e738c340295416b50f39a9463a5635450b9f93c4c06737affd42ae06cee5879c96c0bd58a91345503",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 67,
|
||||||
|
"comment" : "R==0",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027ab98ab862e4e7ec3361a45ac1993e9b47d9ac40db91faed752399cee0413122b47346594fd7d2c8949b43e4cabaf17d8339ea0e307023f",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 68,
|
||||||
|
"comment" : "invalid R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd11bae33a0999fd3fd2bed6fa5577685e8fd595e79c006e58fd35f69f91b1d853553fb4006019a07725aa37773883dbe12253812887ac828",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 69,
|
||||||
|
"comment" : "all bits flipped in R",
|
||||||
|
"msg" : "313233343030",
|
||||||
|
"sig" : "a246b3acefe0ade093e0bc49f15b281f9042b63d175050b033d7619ba1f77f578471aa7a720b30dd6e58cfc0025bb947d5ee84b22bf7300d7f334e48141af0fade1469f5dedb851c9e725d27bd65012bada05e70cde641aad9ce0bea4983164f73816b6f13095e6b93eb03e850cad0cf0d",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 70,
|
||||||
|
"comment" : "checking malleability ",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f280241bd6142ddb02c0f9fa133955d3e610b4b27cb814227de8b241ef4e86402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd9866",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : [
|
||||||
|
"SignatureMalleability"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 71,
|
||||||
|
"comment" : "checking malleability ",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28017602ec0bf9d7be34e8ad9c6c795533244e952675efdcbac9c65b9cb85402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd98a6",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : [
|
||||||
|
"SignatureMalleability"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 72,
|
||||||
|
"comment" : "checking malleability ",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f280fde9de16e5226d2af9a864e2ac1a2d756456ffc4f1b3693570ad4dc584402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd9826",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : [
|
||||||
|
"SignatureMalleability"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 73,
|
||||||
|
"comment" : "checking malleability ",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f280c9fd3fc42f2d50b84de67a197724e0faa43058801821a546173d76b882402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd9826",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : [
|
||||||
|
"SignatureMalleability"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 74,
|
||||||
|
"comment" : "checking malleability ",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd9866",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : [
|
||||||
|
"SignatureMalleability"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 75,
|
||||||
|
"comment" : "checking malleability ",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd98a6",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : [
|
||||||
|
"SignatureMalleability"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 76,
|
||||||
|
"comment" : "checking malleability ",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28031d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d286402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd9826",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : [
|
||||||
|
"SignatureMalleability"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 77,
|
||||||
|
"comment" : "checking malleability ",
|
||||||
|
"msg" : "54657374",
|
||||||
|
"sig" : "5d053ff5b71f6ec3284525d35d77933178c8e19879886d08eccc6c7d27e9e5b5e02537dbc4d4723506e8d171fc1733857573dd02d18f48f28030d67d699a188a9ca46b4eabe2107aef237ca609cb462e24c91d25d285402b6ef7862b78a386950246ff38d6d2f458136d12e3c97fdd9826",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : [
|
||||||
|
"SignatureMalleability"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jwk" : {
|
||||||
|
"crv" : "Ed448",
|
||||||
|
"d" : "bIKlYsuAjRDWMr6JyFE-v2ySnzTd-oyfY8mWDvbjSKNSjIo_zC8ETjmj_FuUSS-PAy51SaIAmPlb",
|
||||||
|
"kid" : "none",
|
||||||
|
"kty" : "OKP",
|
||||||
|
"x" : "X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq_oJWGA"
|
||||||
|
},
|
||||||
|
"key" : {
|
||||||
|
"curve" : "edwards448",
|
||||||
|
"keySize" : 448,
|
||||||
|
"pk" : "5fd7449b59b461fd2ce787ec616ad46a1da1342485a70e1f8a0ea75d80e96778edf124769b46c7061bd6783df1e50f6cd1fa1abeafe8256180",
|
||||||
|
"sk" : "6c82a562cb808d10d632be89c8513ebf6c929f34ddfa8c9f63c9960ef6e348a3528c8a3fcc2f044e39a3fc5b94492f8f032e7549a20098f95b",
|
||||||
|
"type" : "EDDSAKeyPair"
|
||||||
|
},
|
||||||
|
"keyDer" : "3043300506032b6571033a005fd7449b59b461fd2ce787ec616ad46a1da1342485a70e1f8a0ea75d80e96778edf124769b46c7061bd6783df1e50f6cd1fa1abeafe8256180",
|
||||||
|
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGA\n-----END PUBLIC KEY-----\n",
|
||||||
|
"type" : "EddsaVerify",
|
||||||
|
"tests" : [
|
||||||
|
{
|
||||||
|
"tcId" : 78,
|
||||||
|
"comment" : "RFC 8032",
|
||||||
|
"msg" : "",
|
||||||
|
"sig" : "533a37f6bbe457251f023c0d88f976ae2dfb504a843e34d2074fd823d41a591f2b233f034f628281f2fd7a22ddd47d7828c59bd0a21bfd3980ff0d2028d4b18a9df63e006c5d1c2d345b925d8dc00b4104852db99ac5c7cdda8530a113a0f4dbb61149f05a7363268c71d95808ff2e652600",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jwk" : {
|
||||||
|
"crv" : "Ed448",
|
||||||
|
"d" : "xOqwXTVwB8Yy89u0hImSTVUrCP4MNToNSh8ArNosRjr76mfF6NKHfF47w5emWZSe-AIelU4KEidO",
|
||||||
|
"kid" : "none",
|
||||||
|
"kty" : "OKP",
|
||||||
|
"x" : "Q7oo9DDN_0Vq5TFUX37NCsg0pV2TWMA3K_oMbGeYwIZq6gHrAHQoArhDjqTLghacI1FgYntMOpSA"
|
||||||
|
},
|
||||||
|
"key" : {
|
||||||
|
"curve" : "edwards448",
|
||||||
|
"keySize" : 448,
|
||||||
|
"pk" : "43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c0866aea01eb00742802b8438ea4cb82169c235160627b4c3a9480",
|
||||||
|
"sk" : "c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463afbea67c5e8d2877c5e3bc397a659949ef8021e954e0a12274e",
|
||||||
|
"type" : "EDDSAKeyPair"
|
||||||
|
},
|
||||||
|
"keyDer" : "3043300506032b6571033a0043ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c0866aea01eb00742802b8438ea4cb82169c235160627b4c3a9480",
|
||||||
|
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAQ7oo9DDN/0Vq5TFUX37NCsg0pV2TWMA3K/oMbGeYwIZq6gHrAHQoArhDjqTLghacI1FgYntMOpSA\n-----END PUBLIC KEY-----\n",
|
||||||
|
"type" : "EddsaVerify",
|
||||||
|
"tests" : [
|
||||||
|
{
|
||||||
|
"tcId" : 79,
|
||||||
|
"comment" : "RFC 8032: 1 octet",
|
||||||
|
"msg" : "03",
|
||||||
|
"sig" : "26b8f91727bd62897af15e41eb43c377efb9c610d48f2335cb0bd0087810f4352541b143c4b981b7e18f62de8ccdf633fc1bf037ab7cd779805e0dbcc0aae1cbcee1afb2e027df36bc04dcecbf154336c19f0af7e0a6472905e799f1953d2a0ff3348ab21aa4adafd1d234441cf807c03a00",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tcId" : 80,
|
||||||
|
"comment" : "RFC 8032: 1 octet with context",
|
||||||
|
"msg" : "03",
|
||||||
|
"sig" : "d4f8f6131770dd46f40867d6fd5d5055de43541f8c5e35abbcd001b32a89f7d2151f7647f11d8ca2ae279fb842d607217fce6e042f6815ea000c85741de5c8da1144a6a1aba7f96de42505d7a7298524fda538fccbbb754f578c1cad10d54d0d5428407e85dcbc98a49155c13764e66c3c00",
|
||||||
|
"result" : "invalid",
|
||||||
|
"flags" : []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jwk" : {
|
||||||
|
"crv" : "Ed448",
|
||||||
|
"d" : "zSPST3FCdOdENDI3uTKQ9RH2Ql-Y5kRZ_yA-iYUIP_32BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo",
|
||||||
|
"kid" : "none",
|
||||||
|
"kty" : "OKP",
|
||||||
|
"x" : "3OqeePNaG_NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl_OFh1xznExpUPqTLX36fHYsAaWRHABQA"
|
||||||
|
},
|
||||||
|
"key" : {
|
||||||
|
"curve" : "edwards448",
|
||||||
|
"keySize" : 448,
|
||||||
|
"pk" : "dcea9e78f35a1bf3499a831b10b86c90aac01cd84b67a0109b55a36e9328b1e365fce161d71ce7131a543ea4cb5f7e9f1d8b00696447001400",
|
||||||
|
"sk" : "cd23d24f714274e744343237b93290f511f6425f98e64459ff203e8985083ffdf60500553abc0e05cd02184bdb89c4ccd67e187951267eb328",
|
||||||
|
"type" : "EDDSAKeyPair"
|
||||||
|
},
|
||||||
|
"keyDer" : "3043300506032b6571033a00dcea9e78f35a1bf3499a831b10b86c90aac01cd84b67a0109b55a36e9328b1e365fce161d71ce7131a543ea4cb5f7e9f1d8b00696447001400",
|
||||||
|
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoA3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQA\n-----END PUBLIC KEY-----\n",
|
||||||
|
"type" : "EddsaVerify",
|
||||||
|
"tests" : [
|
||||||
|
{
|
||||||
|
"tcId" : 81,
|
||||||
|
"comment" : "RFC 8032: 11 bytes",
|
||||||
|
"msg" : "0c3e544074ec63b0265e0c",
|
||||||
|
"sig" : "1f0a8888ce25e8d458a21130879b840a9089d999aaba039eaf3e3afa090a09d389dba82c4ff2ae8ac5cdfb7c55e94d5d961a29fe0109941e00b8dbdeea6d3b051068df7254c0cdc129cbe62db2dc957dbb47b51fd3f213fb8698f064774250a5028961c9bf8ffd973fe5d5c206492b140e00",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jwk" : {
|
||||||
|
"crv" : "Ed448",
|
||||||
|
"d" : "JYzdStoy7Zyf9U5jdWrlgvuPqyrHIfLI5nanJ2hRPZOfY93bVWCRM_Ka34bsmSncy1LBxf0v9-Ib",
|
||||||
|
"kid" : "none",
|
||||||
|
"kty" : "OKP",
|
||||||
|
"x" : "O6FtoMbyzB8wGHdAdW9eeY1rxfwBXXxjzJUQ7j_UStwk2OlotuRub5TRm5RTYXJr114UnvCYF_WA"
|
||||||
|
},
|
||||||
|
"key" : {
|
||||||
|
"curve" : "edwards448",
|
||||||
|
"keySize" : 448,
|
||||||
|
"pk" : "3ba16da0c6f2cc1f30187740756f5e798d6bc5fc015d7c63cc9510ee3fd44adc24d8e968b6e46e6f94d19b945361726bd75e149ef09817f580",
|
||||||
|
"sk" : "258cdd4ada32ed9c9ff54e63756ae582fb8fab2ac721f2c8e676a72768513d939f63dddb55609133f29adf86ec9929dccb52c1c5fd2ff7e21b",
|
||||||
|
"type" : "EDDSAKeyPair"
|
||||||
|
},
|
||||||
|
"keyDer" : "3043300506032b6571033a003ba16da0c6f2cc1f30187740756f5e798d6bc5fc015d7c63cc9510ee3fd44adc24d8e968b6e46e6f94d19b945361726bd75e149ef09817f580",
|
||||||
|
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAO6FtoMbyzB8wGHdAdW9eeY1rxfwBXXxjzJUQ7j/UStwk2OlotuRub5TRm5RTYXJr114UnvCYF/WA\n-----END PUBLIC KEY-----\n",
|
||||||
|
"type" : "EddsaVerify",
|
||||||
|
"tests" : [
|
||||||
|
{
|
||||||
|
"tcId" : 82,
|
||||||
|
"comment" : "RFC 8032: 12 bytes",
|
||||||
|
"msg" : "64a65f3cdedcdd66811e2915",
|
||||||
|
"sig" : "7eeeab7c4e50fb799b418ee5e3197ff6bf15d43a14c34389b59dd1a7b1b85b4ae90438aca634bea45e3a2695f1270f07fdcdf7c62b8efeaf00b45c2c96ba457eb1a8bf075a3db28e5c24f6b923ed4ad747c3c9e03c7079efb87cb110d3a99861e72003cbae6d6b8b827e4e6c143064ff3c00",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jwk" : {
|
||||||
|
"crv" : "Ed448",
|
||||||
|
"d" : "fvToRUQjZ1L7tWuPMaI6EOQoFPX1XKA3zcwRxkyaOylJwbtgcAMUYRcypsL-qY7rwCZqEak5cBAO",
|
||||||
|
"kid" : "none",
|
||||||
|
"kty" : "OKP",
|
||||||
|
"x" : "s9oHmwqkk6V3ICnwRnuuvuWoES2dOiJTI2HaKU97s4FcXcWeF2tNnzgcoJOOE8bAexdL5l36V46A"
|
||||||
|
},
|
||||||
|
"key" : {
|
||||||
|
"curve" : "edwards448",
|
||||||
|
"keySize" : 448,
|
||||||
|
"pk" : "b3da079b0aa493a5772029f0467baebee5a8112d9d3a22532361da294f7bb3815c5dc59e176b4d9f381ca0938e13c6c07b174be65dfa578e80",
|
||||||
|
"sk" : "7ef4e84544236752fbb56b8f31a23a10e42814f5f55ca037cdcc11c64c9a3b2949c1bb60700314611732a6c2fea98eebc0266a11a93970100e",
|
||||||
|
"type" : "EDDSAKeyPair"
|
||||||
|
},
|
||||||
|
"keyDer" : "3043300506032b6571033a00b3da079b0aa493a5772029f0467baebee5a8112d9d3a22532361da294f7bb3815c5dc59e176b4d9f381ca0938e13c6c07b174be65dfa578e80",
|
||||||
|
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAs9oHmwqkk6V3ICnwRnuuvuWoES2dOiJTI2HaKU97s4FcXcWeF2tNnzgcoJOOE8bAexdL5l36V46A\n-----END PUBLIC KEY-----\n",
|
||||||
|
"type" : "EddsaVerify",
|
||||||
|
"tests" : [
|
||||||
|
{
|
||||||
|
"tcId" : 83,
|
||||||
|
"comment" : "RFC 8032: 13 bytes",
|
||||||
|
"msg" : "64a65f3cdedcdd66811e2915e7",
|
||||||
|
"sig" : "6a12066f55331b6c22acd5d5bfc5d71228fbda80ae8dec26bdd306743c5027cb4890810c162c027468675ecf645a83176c0d7323a2ccde2d80efe5a1268e8aca1d6fbc194d3f77c44986eb4ab4177919ad8bec33eb47bbb5fc6e28196fd1caf56b4e7e0ba5519234d047155ac727a1053100",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jwk" : {
|
||||||
|
"crv" : "Ed448",
|
||||||
|
"d" : "1l3zQa0T4AhWdoi67dqOnc3BfcAkl06ltCJ7ZTDjOb_yH5nmjKaWjzzKbf4PufT6tPoTXVVC6j8B",
|
||||||
|
"kid" : "none",
|
||||||
|
"kty" : "OKP",
|
||||||
|
"x" : "35cF9Y7bq4Asf4Njz-VWCrHGEywgqfHdFjSDom-KxTo51oCL9KHfvSYbCZuwOz-1CQbLKL2KCB8A"
|
||||||
|
},
|
||||||
|
"key" : {
|
||||||
|
"curve" : "edwards448",
|
||||||
|
"keySize" : 448,
|
||||||
|
"pk" : "df9705f58edbab802c7f8363cfe5560ab1c6132c20a9f1dd163483a26f8ac53a39d6808bf4a1dfbd261b099bb03b3fb50906cb28bd8a081f00",
|
||||||
|
"sk" : "d65df341ad13e008567688baedda8e9dcdc17dc024974ea5b4227b6530e339bff21f99e68ca6968f3cca6dfe0fb9f4fab4fa135d5542ea3f01",
|
||||||
|
"type" : "EDDSAKeyPair"
|
||||||
|
},
|
||||||
|
"keyDer" : "3043300506032b6571033a00df9705f58edbab802c7f8363cfe5560ab1c6132c20a9f1dd163483a26f8ac53a39d6808bf4a1dfbd261b099bb03b3fb50906cb28bd8a081f00",
|
||||||
|
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoA35cF9Y7bq4Asf4Njz+VWCrHGEywgqfHdFjSDom+KxTo51oCL9KHfvSYbCZuwOz+1CQbLKL2KCB8A\n-----END PUBLIC KEY-----\n",
|
||||||
|
"type" : "EddsaVerify",
|
||||||
|
"tests" : [
|
||||||
|
{
|
||||||
|
"tcId" : 84,
|
||||||
|
"comment" : "RFC 8032: 64 bytes",
|
||||||
|
"msg" : "bd0f6a3747cd561bdddf4640a332461a4a30a12a434cd0bf40d766d9c6d458e5512204a30c17d1f50b5079631f64eb3112182da3005835461113718d1a5ef944",
|
||||||
|
"sig" : "554bc2480860b49eab8532d2a533b7d578ef473eeb58c98bb2d0e1ce488a98b18dfde9b9b90775e67f47d4a1c3482058efc9f40d2ca033a0801b63d45b3b722ef552bad3b4ccb667da350192b61c508cf7b6b5adadc2c8d9a446ef003fb05cba5f30e88e36ec2703b349ca229c2670833900",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jwk" : {
|
||||||
|
"crv" : "Ed448",
|
||||||
|
"d" : "LsX-PBcEWr2xNqXmqRPjKrda5otT0vwUm3flBBMtN1abfnZrp0oZvWFiNDohyFkKqc68qQFMY231",
|
||||||
|
"kid" : "none",
|
||||||
|
"kty" : "OKP",
|
||||||
|
"x" : "eXVvAU3P4gefXdnnGL5BceLvJIagjyUYb2v_Q6mTa5v-EkArCK5leYo9geIunsgOdpCGLvPU7ToA"
|
||||||
|
},
|
||||||
|
"key" : {
|
||||||
|
"curve" : "edwards448",
|
||||||
|
"keySize" : 448,
|
||||||
|
"pk" : "79756f014dcfe2079f5dd9e718be4171e2ef2486a08f25186f6bff43a9936b9bfe12402b08ae65798a3d81e22e9ec80e7690862ef3d4ed3a00",
|
||||||
|
"sk" : "2ec5fe3c17045abdb136a5e6a913e32ab75ae68b53d2fc149b77e504132d37569b7e766ba74a19bd6162343a21c8590aa9cebca9014c636df5",
|
||||||
|
"type" : "EDDSAKeyPair"
|
||||||
|
},
|
||||||
|
"keyDer" : "3043300506032b6571033a0079756f014dcfe2079f5dd9e718be4171e2ef2486a08f25186f6bff43a9936b9bfe12402b08ae65798a3d81e22e9ec80e7690862ef3d4ed3a00",
|
||||||
|
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAeXVvAU3P4gefXdnnGL5BceLvJIagjyUYb2v/Q6mTa5v+EkArCK5leYo9geIunsgOdpCGLvPU7ToA\n-----END PUBLIC KEY-----\n",
|
||||||
|
"type" : "EddsaVerify",
|
||||||
|
"tests" : [
|
||||||
|
{
|
||||||
|
"tcId" : 85,
|
||||||
|
"comment" : "RFC 8032: 256 bytes",
|
||||||
|
"msg" : "15777532b0bdd0d1389f636c5f6b9ba734c90af572877e2d272dd078aa1e567cfa80e12928bb542330e8409f3174504107ecd5efac61ae7504dabe2a602ede89e5cca6257a7c77e27a702b3ae39fc769fc54f2395ae6a1178cab4738e543072fc1c177fe71e92e25bf03e4ecb72f47b64d0465aaea4c7fad372536c8ba516a6039c3c2a39f0e4d832be432dfa9a706a6e5c7e19f397964ca4258002f7c0541b590316dbc5622b6b2a6fe7a4abffd96105eca76ea7b98816af0748c10df048ce012d901015a51f189f3888145c03650aa23ce894c3bd889e030d565071c59f409a9981b51878fd6fc110624dcbcde0bf7a69ccce38fabdf86f3bef6044819de11",
|
||||||
|
"sig" : "c650ddbb0601c19ca11439e1640dd931f43c518ea5bea70d3dcde5f4191fe53f00cf966546b72bcc7d58be2b9badef28743954e3a44a23f880e8d4f1cfce2d7a61452d26da05896f0a50da66a239a8a188b6d825b3305ad77b73fbac0836ecc60987fd08527c1a8e80d5823e65cafe2a3d00",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"jwk" : {
|
||||||
|
"crv" : "Ed448",
|
||||||
|
"d" : "hy0JN4D103MN98ISZks3uKDyT1aBDaqDgs1Po_d2NOxE3FTxwu2b6ob6-3Yy2L4ZnqFl9a1V3Zzo",
|
||||||
|
"kid" : "none",
|
||||||
|
"kty" : "OKP",
|
||||||
|
"x" : "qBsuinClrJT_28ybrfw_6wgB8lhXi7EUrUTs4ewOeZ2gjv-4HF1oXAxW9k7srvjN8RzDhzeDjPQA"
|
||||||
|
},
|
||||||
|
"key" : {
|
||||||
|
"curve" : "edwards448",
|
||||||
|
"keySize" : 448,
|
||||||
|
"pk" : "a81b2e8a70a5ac94ffdbcc9badfc3feb0801f258578bb114ad44ece1ec0e799da08effb81c5d685c0c56f64eecaef8cdf11cc38737838cf400",
|
||||||
|
"sk" : "872d093780f5d3730df7c212664b37b8a0f24f56810daa8382cd4fa3f77634ec44dc54f1c2ed9bea86fafb7632d8be199ea165f5ad55dd9ce8",
|
||||||
|
"type" : "EDDSAKeyPair"
|
||||||
|
},
|
||||||
|
"keyDer" : "3043300506032b6571033a00a81b2e8a70a5ac94ffdbcc9badfc3feb0801f258578bb114ad44ece1ec0e799da08effb81c5d685c0c56f64eecaef8cdf11cc38737838cf400",
|
||||||
|
"keyPem" : "-----BEGIN PUBLIC KEY-----\nMEMwBQYDK2VxAzoAqBsuinClrJT/28ybrfw/6wgB8lhXi7EUrUTs4ewOeZ2gjv+4HF1oXAxW9k7srvjN8RzDhzeDjPQA\n-----END PUBLIC KEY-----\n",
|
||||||
|
"type" : "EddsaVerify",
|
||||||
|
"tests" : [
|
||||||
|
{
|
||||||
|
"tcId" : 86,
|
||||||
|
"comment" : "RFC 8032: 1023 bytes",
|
||||||
|
"msg" : "6ddf802e1aae4986935f7f981ba3f0351d6273c0a0c22c9c0e8339168e675412a3debfaf435ed651558007db4384b650fcc07e3b586a27a4f7a00ac8a6fec2cd86ae4bf1570c41e6a40c931db27b2faa15a8cedd52cff7362c4e6e23daec0fbc3a79b6806e316efcc7b68119bf46bc76a26067a53f296dafdbdc11c77f7777e972660cf4b6a9b369a6665f02e0cc9b6edfad136b4fabe723d2813db3136cfde9b6d044322fee2947952e031b73ab5c603349b307bdc27bc6cb8b8bbd7bd323219b8033a581b59eadebb09b3c4f3d2277d4f0343624acc817804728b25ab797172b4c5c21a22f9c7839d64300232eb66e53f31c723fa37fe387c7d3e50bdf9813a30e5bb12cf4cd930c40cfb4e1fc622592a49588794494d56d24ea4b40c89fc0596cc9ebb961c8cb10adde976a5d602b1c3f85b9b9a001ed3c6a4d3b1437f52096cd1956d042a597d561a596ecd3d1735a8d570ea0ec27225a2c4aaff26306d1526c1af3ca6d9cf5a2c98f47e1c46db9a33234cfd4d81f2c98538a09ebe76998d0d8fd25997c7d255c6d66ece6fa56f11144950f027795e653008f4bd7ca2dee85d8e90f3dc315130ce2a00375a318c7c3d97be2c8ce5b6db41a6254ff264fa6155baee3b0773c0f497c573f19bb4f4240281f0b1f4f7be857a4e59d416c06b4c50fa09e1810ddc6b1467baeac5a3668d11b6ecaa901440016f389f80acc4db977025e7f5924388c7e340a732e554440e76570f8dd71b7d640b3450d1fd5f0410a18f9a3494f707c717b79b4bf75c98400b096b21653b5d217cf3565c9597456f70703497a078763829bc01bb1cbc8fa04eadc9a6e3f6699587a9e75c94e5bab0036e0b2e711392cff0047d0d6b05bd2a588bc109718954259f1d86678a579a3120f19cfb2963f177aeb70f2d4844826262e51b80271272068ef5b3856fa8535aa2a88b2d41f2a0e2fda7624c2850272ac4a2f561f8f2f7a318bfd5caf9696149e4ac824ad3460538fdc25421beec2cc6818162d06bbed0c40a387192349db67a118bada6cd5ab0140ee273204f628aad1c135f770279a651e24d8c14d75a6059d76b96a6fd857def5e0b354b27ab937a5815d16b5fae407ff18222c6d1ed263be68c95f32d908bd895cd76207ae726487567f9a67dad79abec316f683b17f2d02bf07e0ac8b5bc6162cf94697b3c27cd1fea49b27f23ba2901871962506520c392da8b6ad0d99f7013fbc06c2c17a569500c8a7696481c1cd33e9b14e40b82e79a5f5db82571ba97bae3ad3e0479515bb0e2b0f3bfcd1fd33034efc6245eddd7ee2086ddae2600d8ca73e214e8c2b0bdb2b047c6a464a562ed77b73d2d841c4b34973551257713b753632efba348169abc90a68f42611a40126d7cb21b58695568186f7e569d2ff0f9e745d0487dd2eb997cafc5abf9dd102e62ff66cba87",
|
||||||
|
"sig" : "e301345a41a39a4d72fff8df69c98075a0cc082b802fc9b2b6bc503f926b65bddf7f4c8f1cb49f6396afc8a70abe6d8aef0db478d4c6b2970076c6a0484fe76d76b3a97625d79f1ce240e7c576750d295528286f719b413de9ada3e8eb78ed573603ce30d8bb761785dc30dbc320869e1a00",
|
||||||
|
"result" : "valid",
|
||||||
|
"flags" : []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
2262
curve-definitions/test/wycheproof/eddsa_test.json
Normal file
2262
curve-definitions/test/wycheproof/eddsa_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5248
curve-definitions/test/wycheproof/x25519_test.json
Normal file
5248
curve-definitions/test/wycheproof/x25519_test.json
Normal file
File diff suppressed because it is too large
Load Diff
5164
curve-definitions/test/wycheproof/x448_test.json
Normal file
5164
curve-definitions/test/wycheproof/x448_test.json
Normal file
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@noble/curves",
|
"name": "@noble/curves",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"description": "Minimal, zero-dependency JS implementation of elliptic curve cryptography",
|
"description": "Minimal, zero-dependency JS implementation of elliptic curve cryptography",
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"lib/esm"
|
"lib/esm"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bench": "node test/benchmark/index.js",
|
"bench": "node curve-definitions/benchmark/index.js",
|
||||||
"build": "tsc && tsc -p tsconfig.esm.json",
|
"build": "tsc && tsc -p tsconfig.esm.json",
|
||||||
"build:release": "rollup -c rollup.config.js",
|
"build:release": "rollup -c rollup.config.js",
|
||||||
"lint": "prettier --check 'src/**/*.{js,ts}' 'curve-definitions/src/**/*.{js,ts}'",
|
"lint": "prettier --check 'src/**/*.{js,ts}' 'curve-definitions/src/**/*.{js,ts}'",
|
||||||
@@ -32,15 +32,25 @@
|
|||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
"./edwards": {
|
||||||
|
"types": "./lib/edwards.d.ts",
|
||||||
|
"import": "./lib/esm/edwards.js",
|
||||||
|
"default": "./lib/edwards.js"
|
||||||
|
},
|
||||||
"./modular": {
|
"./modular": {
|
||||||
"types": "./lib/modular.d.ts",
|
"types": "./lib/modular.d.ts",
|
||||||
"import": "./lib/esm/modular.js",
|
"import": "./lib/esm/modular.js",
|
||||||
"default": "./lib/modular.js"
|
"default": "./lib/modular.js"
|
||||||
},
|
},
|
||||||
"./shortw": {
|
"./montgomery": {
|
||||||
"types": "./lib/shortw.d.ts",
|
"types": "./lib/montgomery.d.ts",
|
||||||
"import": "./lib/esm/shortw.js",
|
"import": "./lib/esm/montgomery.js",
|
||||||
"default": "./lib/shortw.js"
|
"default": "./lib/montgomery.js"
|
||||||
|
},
|
||||||
|
"./weierstrass": {
|
||||||
|
"types": "./lib/weierstrass.d.ts",
|
||||||
|
"import": "./lib/esm/weierstrass.js",
|
||||||
|
"default": "./lib/weierstrass.js"
|
||||||
},
|
},
|
||||||
"./utils": {
|
"./utils": {
|
||||||
"types": "./lib/utils.d.ts",
|
"types": "./lib/utils.d.ts",
|
||||||
@@ -53,14 +63,16 @@
|
|||||||
"curve",
|
"curve",
|
||||||
"cryptography",
|
"cryptography",
|
||||||
"hyperelliptic",
|
"hyperelliptic",
|
||||||
|
"weierstrass",
|
||||||
|
"edwards",
|
||||||
|
"montgomery",
|
||||||
|
"secp256k1",
|
||||||
|
"ed25519",
|
||||||
|
"ed448",
|
||||||
"p256",
|
"p256",
|
||||||
"p384",
|
"p384",
|
||||||
"p521",
|
"p521",
|
||||||
"nist",
|
"nist",
|
||||||
"weierstrass",
|
|
||||||
"edwards",
|
|
||||||
"montgomery",
|
|
||||||
"hashes",
|
|
||||||
"ecc",
|
"ecc",
|
||||||
"ecdsa",
|
"ecdsa",
|
||||||
"eddsa",
|
"eddsa",
|
||||||
@@ -72,4 +84,4 @@
|
|||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
684
src/edwards.ts
Normal file
684
src/edwards.ts
Normal file
@@ -0,0 +1,684 @@
|
|||||||
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
// Implementation of Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y²
|
||||||
|
|
||||||
|
// Differences from @noble/ed25519 1.7:
|
||||||
|
// 1. Different field element lengths in ed448:
|
||||||
|
// EDDSA (RFC8032) is 456 bits / 57 bytes, ECDH (RFC7748) is 448 bits / 56 bytes
|
||||||
|
// 2. Different addition formula (doubling is same)
|
||||||
|
// 3. uvRatio differs between curves (half-expected, not only pow fn changes)
|
||||||
|
// 4. Point decompression code is different too (unexpected), now using generalized formula
|
||||||
|
// 5. Domain function was no-op for ed25519, but adds some data even with empty context for ed448
|
||||||
|
|
||||||
|
import * as mod from './modular.js';
|
||||||
|
import {
|
||||||
|
bytesToHex,
|
||||||
|
concatBytes,
|
||||||
|
ensureBytes,
|
||||||
|
numberToBytesLE,
|
||||||
|
bytesToNumberLE,
|
||||||
|
hashToPrivateScalar,
|
||||||
|
BasicCurve,
|
||||||
|
validateOpts as utilOpts,
|
||||||
|
Hex,
|
||||||
|
PrivKey,
|
||||||
|
} from './utils.js'; // TODO: import * as u from './utils.js'?
|
||||||
|
import { Group, GroupConstructor, wNAF } from './group.js';
|
||||||
|
|
||||||
|
// Be friendly to bad ECMAScript parsers by not using bigint literals like 123n
|
||||||
|
const _0n = BigInt(0);
|
||||||
|
const _1n = BigInt(1);
|
||||||
|
const _2n = BigInt(2);
|
||||||
|
const _8n = BigInt(8);
|
||||||
|
|
||||||
|
export type CHash = {
|
||||||
|
(message: Uint8Array | string): Uint8Array;
|
||||||
|
blockLen: number;
|
||||||
|
outputLen: number;
|
||||||
|
create(): any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CurveType = BasicCurve & {
|
||||||
|
// Params: a, d
|
||||||
|
a: bigint;
|
||||||
|
d: bigint;
|
||||||
|
// Hashes
|
||||||
|
hash: CHash; // Because we need outputLen for DRBG
|
||||||
|
randomBytes: (bytesLength?: number) => Uint8Array;
|
||||||
|
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
|
||||||
|
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
|
||||||
|
uvRatio?: (u: bigint, v: bigint) => { isValid: boolean; value: bigint };
|
||||||
|
preHash?: CHash;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Should be separate from overrides, since overrides can use information about curve (for example nBits)
|
||||||
|
function validateOpts(curve: CurveType) {
|
||||||
|
const opts = utilOpts(curve);
|
||||||
|
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
|
||||||
|
throw new Error('Invalid hash function');
|
||||||
|
for (const i of ['a', 'd'] as const) {
|
||||||
|
if (typeof opts[i] !== 'bigint')
|
||||||
|
throw new Error(`Invalid curve param ${i}=${opts[i]} (${typeof opts[i]})`);
|
||||||
|
}
|
||||||
|
for (const fn of ['randomBytes'] as const) {
|
||||||
|
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
||||||
|
}
|
||||||
|
for (const fn of ['adjustScalarBytes', 'domain', 'uvRatio'] as const) {
|
||||||
|
if (opts[fn] === undefined) continue; // Optional
|
||||||
|
if (typeof opts[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
||||||
|
}
|
||||||
|
// Set defaults
|
||||||
|
return Object.freeze({ ...opts } as const);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance
|
||||||
|
export interface SignatureType {
|
||||||
|
readonly r: PointType;
|
||||||
|
readonly s: bigint;
|
||||||
|
assertValidity(): SignatureType;
|
||||||
|
toRawBytes(): Uint8Array;
|
||||||
|
toHex(): string;
|
||||||
|
}
|
||||||
|
// Static methods
|
||||||
|
export type SignatureConstructor = {
|
||||||
|
new (r: PointType, s: bigint): SignatureType;
|
||||||
|
fromHex(hex: Hex): SignatureType;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Instance
|
||||||
|
export interface ExtendedPointType extends Group<ExtendedPointType> {
|
||||||
|
readonly x: bigint;
|
||||||
|
readonly y: bigint;
|
||||||
|
readonly z: bigint;
|
||||||
|
readonly t: bigint;
|
||||||
|
multiply(scalar: number | bigint, affinePoint?: PointType): ExtendedPointType;
|
||||||
|
multiplyUnsafe(scalar: number | bigint): ExtendedPointType;
|
||||||
|
isSmallOrder(): boolean;
|
||||||
|
isTorsionFree(): boolean;
|
||||||
|
toAffine(invZ?: bigint): PointType;
|
||||||
|
}
|
||||||
|
// Static methods
|
||||||
|
export interface ExtendedPointConstructor extends GroupConstructor<ExtendedPointType> {
|
||||||
|
new (x: bigint, y: bigint, z: bigint, t: bigint): ExtendedPointType;
|
||||||
|
fromAffine(p: PointType): ExtendedPointType;
|
||||||
|
toAffineBatch(points: ExtendedPointType[]): PointType[];
|
||||||
|
normalizeZ(points: ExtendedPointType[]): ExtendedPointType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance
|
||||||
|
export interface PointType extends Group<PointType> {
|
||||||
|
readonly x: bigint;
|
||||||
|
readonly y: bigint;
|
||||||
|
_setWindowSize(windowSize: number): void;
|
||||||
|
toRawBytes(isCompressed?: boolean): Uint8Array;
|
||||||
|
toHex(isCompressed?: boolean): string;
|
||||||
|
isTorsionFree(): boolean;
|
||||||
|
}
|
||||||
|
// Static methods
|
||||||
|
export interface PointConstructor extends GroupConstructor<PointType> {
|
||||||
|
new (x: bigint, y: bigint): PointType;
|
||||||
|
fromHex(hex: Hex): PointType;
|
||||||
|
fromPrivateKey(privateKey: PrivKey): PointType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PubKey = Hex | PointType;
|
||||||
|
export type SigType = Hex | SignatureType;
|
||||||
|
|
||||||
|
export type CurveFn = {
|
||||||
|
CURVE: ReturnType<typeof validateOpts>;
|
||||||
|
getPublicKey: (privateKey: PrivKey, isCompressed?: boolean) => Uint8Array;
|
||||||
|
sign: (message: Hex, privateKey: Hex) => Uint8Array;
|
||||||
|
verify: (sig: SigType, message: Hex, publicKey: PubKey) => boolean;
|
||||||
|
Point: PointConstructor;
|
||||||
|
ExtendedPoint: ExtendedPointConstructor;
|
||||||
|
Signature: SignatureConstructor;
|
||||||
|
utils: {
|
||||||
|
mod: (a: bigint, b?: bigint) => bigint;
|
||||||
|
invert: (number: bigint, modulo?: bigint) => bigint;
|
||||||
|
randomPrivateKey: () => Uint8Array;
|
||||||
|
getExtendedPublicKey: (key: PrivKey) => {
|
||||||
|
head: Uint8Array;
|
||||||
|
prefix: Uint8Array;
|
||||||
|
scalar: bigint;
|
||||||
|
point: PointType;
|
||||||
|
pointBytes: Uint8Array;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: it is not generic twisted curve for now, but ed25519/ed448 generic implementation
|
||||||
|
export function twistedEdwards(curveDef: CurveType): CurveFn {
|
||||||
|
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
|
||||||
|
const CURVE_ORDER = CURVE.n;
|
||||||
|
const fieldLen = CURVE.nByteLength; // 32 (length of one field element)
|
||||||
|
if (fieldLen > 2048) throw new Error('Field lengths over 2048 are not supported');
|
||||||
|
const groupLen = CURVE.nByteLength;
|
||||||
|
// (2n ** 256n).toString(16);
|
||||||
|
const maxGroupElement = _2n ** BigInt(groupLen * 8); // previous POW_2_256
|
||||||
|
|
||||||
|
// Function overrides
|
||||||
|
const { P, randomBytes } = CURVE;
|
||||||
|
const modP = (a: bigint) => mod.mod(a, P);
|
||||||
|
|
||||||
|
// sqrt(u/v)
|
||||||
|
function _uvRatio(u: bigint, v: bigint) {
|
||||||
|
try {
|
||||||
|
const value = mod.sqrt(u * mod.invert(v, P), P);
|
||||||
|
return { isValid: true, value };
|
||||||
|
} catch (e) {
|
||||||
|
return { isValid: false, value: _0n };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const uvRatio = CURVE.uvRatio || _uvRatio;
|
||||||
|
|
||||||
|
const _adjustScalarBytes = (bytes: Uint8Array) => bytes; // NOOP
|
||||||
|
const adjustScalarBytes = CURVE.adjustScalarBytes || _adjustScalarBytes;
|
||||||
|
function _domain(data: Uint8Array, ctx: Uint8Array, phflag: boolean) {
|
||||||
|
if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported');
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
const domain = CURVE.domain || _domain; // NOOP
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended Point works in extended coordinates: (x, y, z, t) ∋ (x=x/z, y=y/z, t=xy).
|
||||||
|
* Default Point works in affine coordinates: (x, y)
|
||||||
|
* https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates
|
||||||
|
*/
|
||||||
|
class ExtendedPoint implements ExtendedPointType {
|
||||||
|
constructor(readonly x: bigint, readonly y: bigint, readonly z: bigint, readonly t: bigint) {}
|
||||||
|
|
||||||
|
static BASE = new ExtendedPoint(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy));
|
||||||
|
static ZERO = new ExtendedPoint(_0n, _1n, _1n, _0n);
|
||||||
|
static fromAffine(p: Point): ExtendedPoint {
|
||||||
|
if (!(p instanceof Point)) {
|
||||||
|
throw new TypeError('ExtendedPoint#fromAffine: expected Point');
|
||||||
|
}
|
||||||
|
if (p.equals(Point.ZERO)) return ExtendedPoint.ZERO;
|
||||||
|
return new ExtendedPoint(p.x, p.y, _1n, modP(p.x * p.y));
|
||||||
|
}
|
||||||
|
// Takes a bunch of Jacobian Points but executes only one
|
||||||
|
// invert on all of them. invert is very slow operation,
|
||||||
|
// so this improves performance massively.
|
||||||
|
static toAffineBatch(points: ExtendedPoint[]): Point[] {
|
||||||
|
const toInv = mod.invertBatch(
|
||||||
|
points.map((p) => p.z),
|
||||||
|
P
|
||||||
|
);
|
||||||
|
return points.map((p, i) => p.toAffine(toInv[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static normalizeZ(points: ExtendedPoint[]): ExtendedPoint[] {
|
||||||
|
return this.toAffineBatch(points).map(this.fromAffine);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare one point to another.
|
||||||
|
equals(other: ExtendedPoint): boolean {
|
||||||
|
assertExtPoint(other);
|
||||||
|
const { x: X1, y: Y1, z: Z1 } = this;
|
||||||
|
const { x: X2, y: Y2, z: Z2 } = other;
|
||||||
|
const X1Z2 = modP(X1 * Z2);
|
||||||
|
const X2Z1 = modP(X2 * Z1);
|
||||||
|
const Y1Z2 = modP(Y1 * Z2);
|
||||||
|
const Y2Z1 = modP(Y2 * Z1);
|
||||||
|
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inverses point to one corresponding to (x, -y) in Affine coordinates.
|
||||||
|
negate(): ExtendedPoint {
|
||||||
|
return new ExtendedPoint(modP(-this.x), this.y, this.z, modP(-this.t));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast algo for doubling Extended Point.
|
||||||
|
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd
|
||||||
|
// Cost: 4M + 4S + 1*a + 6add + 1*2.
|
||||||
|
double(): ExtendedPoint {
|
||||||
|
const { a } = CURVE;
|
||||||
|
const { x: X1, y: Y1, z: Z1 } = this;
|
||||||
|
const A = modP(X1 * X1); // A = X12
|
||||||
|
const B = modP(Y1 * Y1); // B = Y12
|
||||||
|
const C = modP(_2n * modP(Z1 * Z1)); // C = 2*Z12
|
||||||
|
const D = modP(a * A); // D = a*A
|
||||||
|
const x1y1 = X1 + Y1;
|
||||||
|
const E = modP(modP(x1y1 * x1y1) - A - B); // E = (X1+Y1)2-A-B
|
||||||
|
const G = D + B; // G = D+B
|
||||||
|
const F = G - C; // F = G-C
|
||||||
|
const H = D - B; // H = D-B
|
||||||
|
const X3 = modP(E * F); // X3 = E*F
|
||||||
|
const Y3 = modP(G * H); // Y3 = G*H
|
||||||
|
const T3 = modP(E * H); // T3 = E*H
|
||||||
|
const Z3 = modP(F * G); // Z3 = F*G
|
||||||
|
return new ExtendedPoint(X3, Y3, Z3, T3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast algo for adding 2 Extended Points.
|
||||||
|
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd
|
||||||
|
// Cost: 9M + 1*a + 1*d + 7add.
|
||||||
|
add(other: ExtendedPoint) {
|
||||||
|
assertExtPoint(other);
|
||||||
|
const { a, d } = CURVE;
|
||||||
|
const { x: X1, y: Y1, z: Z1, t: T1 } = this;
|
||||||
|
const { x: X2, y: Y2, z: Z2, t: T2 } = other;
|
||||||
|
// Faster algo for adding 2 Extended Points when curve's a=-1.
|
||||||
|
// http://hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-4
|
||||||
|
// Cost: 8M + 8add + 2*2.
|
||||||
|
// Note: It does not check whether the `other` point is valid.
|
||||||
|
if (a === BigInt(-1)) {
|
||||||
|
const A = modP((Y1 - X1) * (Y2 + X2));
|
||||||
|
const B = modP((Y1 + X1) * (Y2 - X2));
|
||||||
|
const F = modP(B - A);
|
||||||
|
if (F === _0n) return this.double(); // Same point.
|
||||||
|
const C = modP(Z1 * _2n * T2);
|
||||||
|
const D = modP(T1 * _2n * Z2);
|
||||||
|
const E = D + C;
|
||||||
|
const G = B + A;
|
||||||
|
const H = D - C;
|
||||||
|
const X3 = modP(E * F);
|
||||||
|
const Y3 = modP(G * H);
|
||||||
|
const T3 = modP(E * H);
|
||||||
|
const Z3 = modP(F * G);
|
||||||
|
return new ExtendedPoint(X3, Y3, Z3, T3);
|
||||||
|
}
|
||||||
|
const A = modP(X1 * X2); // A = X1*X2
|
||||||
|
const B = modP(Y1 * Y2); // B = Y1*Y2
|
||||||
|
const C = modP(T1 * d * T2); // C = T1*d*T2
|
||||||
|
const D = modP(Z1 * Z2); // D = Z1*Z2
|
||||||
|
const E = modP((X1 + Y1) * (X2 + Y2) - A - B); // E = (X1+Y1)*(X2+Y2)-A-B
|
||||||
|
// TODO: do we need to check for same point here? Looks like working without it
|
||||||
|
const F = D - C; // F = D-C
|
||||||
|
const G = D + C; // G = D+C
|
||||||
|
const H = modP(B - a * A); // H = B-a*A
|
||||||
|
const X3 = modP(E * F); // X3 = E*F
|
||||||
|
const Y3 = modP(G * H); // Y3 = G*H
|
||||||
|
const T3 = modP(E * H); // T3 = E*H
|
||||||
|
const Z3 = modP(F * G); // Z3 = F*G
|
||||||
|
|
||||||
|
return new ExtendedPoint(X3, Y3, Z3, T3);
|
||||||
|
}
|
||||||
|
|
||||||
|
subtract(other: ExtendedPoint): ExtendedPoint {
|
||||||
|
return this.add(other.negate());
|
||||||
|
}
|
||||||
|
|
||||||
|
private wNAF(n: bigint, affinePoint?: Point): ExtendedPoint {
|
||||||
|
if (!affinePoint && this.equals(ExtendedPoint.BASE)) affinePoint = Point.BASE;
|
||||||
|
const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1;
|
||||||
|
let precomputes = affinePoint && pointPrecomputes.get(affinePoint);
|
||||||
|
if (!precomputes) {
|
||||||
|
precomputes = wnaf.precomputeWindow(this, W) as ExtendedPoint[];
|
||||||
|
if (affinePoint && W !== 1) {
|
||||||
|
precomputes = ExtendedPoint.normalizeZ(precomputes);
|
||||||
|
pointPrecomputes.set(affinePoint, precomputes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { p, f } = wnaf.wNAF(W, precomputes, n);
|
||||||
|
return ExtendedPoint.normalizeZ([p, f])[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constant time multiplication.
|
||||||
|
// Uses wNAF method. Windowed method may be 10% faster,
|
||||||
|
// but takes 2x longer to generate and consumes 2x memory.
|
||||||
|
multiply(scalar: number | bigint, affinePoint?: Point): ExtendedPoint {
|
||||||
|
return this.wNAF(normalizeScalar(scalar, CURVE_ORDER), affinePoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-constant-time multiplication. Uses double-and-add algorithm.
|
||||||
|
// It's faster, but should only be used when you don't care about
|
||||||
|
// an exposed private key e.g. sig verification.
|
||||||
|
// Allows scalar bigger than curve order, but less than 2^256
|
||||||
|
multiplyUnsafe(scalar: number | bigint): ExtendedPoint {
|
||||||
|
let n = normalizeScalar(scalar, CURVE_ORDER, false);
|
||||||
|
const G = ExtendedPoint.BASE;
|
||||||
|
const P0 = ExtendedPoint.ZERO;
|
||||||
|
if (n === _0n) return P0;
|
||||||
|
if (this.equals(P0) || n === _1n) return this;
|
||||||
|
if (this.equals(G)) return this.wNAF(n);
|
||||||
|
return wnaf.unsafeLadder(this, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiplies point by cofactor and checks if the result is 0.
|
||||||
|
isSmallOrder(): boolean {
|
||||||
|
return this.multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiplies point by a very big scalar n and checks if the result is 0.
|
||||||
|
isTorsionFree(): boolean {
|
||||||
|
return this.multiplyUnsafe(CURVE_ORDER).equals(ExtendedPoint.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts Extended point to default (x, y) coordinates.
|
||||||
|
// Can accept precomputed Z^-1 - for example, from invertBatch.
|
||||||
|
toAffine(invZ?: bigint): Point {
|
||||||
|
const { x, y, z } = this;
|
||||||
|
const is0 = this.equals(ExtendedPoint.ZERO);
|
||||||
|
if (invZ == null) invZ = is0 ? _8n : mod.invert(z, P); // 8 was chosen arbitrarily
|
||||||
|
const ax = modP(x * invZ);
|
||||||
|
const ay = modP(y * invZ);
|
||||||
|
const zz = modP(z * invZ);
|
||||||
|
if (is0) return Point.ZERO;
|
||||||
|
if (zz !== _1n) throw new Error('invZ was invalid');
|
||||||
|
return new Point(ax, ay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const wnaf = wNAF(ExtendedPoint, groupLen * 8);
|
||||||
|
|
||||||
|
function assertExtPoint(other: unknown) {
|
||||||
|
if (!(other instanceof ExtendedPoint)) throw new TypeError('ExtendedPoint expected');
|
||||||
|
}
|
||||||
|
// Stores precomputed values for points.
|
||||||
|
const pointPrecomputes = new WeakMap<Point, ExtendedPoint[]>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default Point works in affine coordinates: (x, y)
|
||||||
|
*/
|
||||||
|
class Point implements PointType {
|
||||||
|
// Base point aka generator
|
||||||
|
// public_key = Point.BASE * private_key
|
||||||
|
static BASE: Point = new Point(CURVE.Gx, CURVE.Gy);
|
||||||
|
// Identity point aka point at infinity
|
||||||
|
// point = point + zero_point
|
||||||
|
static ZERO: Point = new Point(_0n, _1n);
|
||||||
|
// We calculate precomputes for elliptic curve point multiplication
|
||||||
|
// using windowed method. This specifies window size and
|
||||||
|
// stores precomputed values. Usually only base point would be precomputed.
|
||||||
|
_WINDOW_SIZE?: number;
|
||||||
|
|
||||||
|
constructor(readonly x: bigint, readonly y: bigint) {}
|
||||||
|
|
||||||
|
// "Private method", don't use it directly.
|
||||||
|
_setWindowSize(windowSize: number) {
|
||||||
|
this._WINDOW_SIZE = windowSize;
|
||||||
|
pointPrecomputes.delete(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts hash string or Uint8Array to Point.
|
||||||
|
// Uses algo from RFC8032 5.1.3.
|
||||||
|
static fromHex(hex: Hex, strict = true) {
|
||||||
|
const { d, P, a } = CURVE;
|
||||||
|
hex = ensureBytes(hex, fieldLen);
|
||||||
|
// 1. First, interpret the string as an integer in little-endian
|
||||||
|
// representation. Bit 255 of this number is the least significant
|
||||||
|
// bit of the x-coordinate and denote this value x_0. The
|
||||||
|
// y-coordinate is recovered simply by clearing this bit. If the
|
||||||
|
// resulting value is >= p, decoding fails.
|
||||||
|
const normed = hex.slice();
|
||||||
|
const lastByte = hex[fieldLen - 1];
|
||||||
|
normed[fieldLen - 1] = lastByte & ~0x80;
|
||||||
|
const y = bytesToNumberLE(normed);
|
||||||
|
|
||||||
|
if (strict && y >= P) throw new Error('Expected 0 < hex < P');
|
||||||
|
if (!strict && y >= maxGroupElement) throw new Error('Expected 0 < hex < 2**256');
|
||||||
|
|
||||||
|
// 2. To recover the x-coordinate, the curve equation implies
|
||||||
|
// Ed25519: x² = (y² - 1) / (d y² + 1) (mod p).
|
||||||
|
// Ed448: x² = (y² - 1) / (d y² - 1) (mod p).
|
||||||
|
// For generic case:
|
||||||
|
// a*x²+y²=1+d*x²*y²
|
||||||
|
// -> y²-1 = d*x²*y²-a*x²
|
||||||
|
// -> y²-1 = x² (d*y²-a)
|
||||||
|
// -> x² = (y²-1) / (d*y²-a)
|
||||||
|
|
||||||
|
// The denominator is always non-zero mod p. Let u = y² - 1 and v = d y² + 1.
|
||||||
|
const y2 = modP(y * y);
|
||||||
|
const u = modP(y2 - _1n);
|
||||||
|
const v = modP(d * y2 - a);
|
||||||
|
let { isValid, value: x } = uvRatio(u, v);
|
||||||
|
if (!isValid) throw new Error('Point.fromHex: invalid y coordinate');
|
||||||
|
// 4. Finally, use the x_0 bit to select the right square root. If
|
||||||
|
// x = 0, and x_0 = 1, decoding fails. Otherwise, if x_0 != x mod
|
||||||
|
// 2, set x <-- p - x. Return the decoded point (x,y).
|
||||||
|
const isXOdd = (x & _1n) === _1n;
|
||||||
|
const isLastByteOdd = (lastByte & 0x80) !== 0;
|
||||||
|
if (isLastByteOdd !== isXOdd) x = modP(-x);
|
||||||
|
return new Point(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromPrivateKey(privateKey: PrivKey) {
|
||||||
|
return getExtendedPublicKey(privateKey).point;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There can always be only two x values (x, -x) for any y
|
||||||
|
// When compressing point, it's enough to only store its y coordinate
|
||||||
|
// and use the last byte to encode sign of x.
|
||||||
|
toRawBytes(): Uint8Array {
|
||||||
|
const bytes = numberToBytesLE(this.y, fieldLen);
|
||||||
|
bytes[fieldLen - 1] |= this.x & _1n ? 0x80 : 0;
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as toRawBytes, but returns string.
|
||||||
|
toHex(): string {
|
||||||
|
return bytesToHex(this.toRawBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
isTorsionFree(): boolean {
|
||||||
|
return ExtendedPoint.fromAffine(this).isTorsionFree();
|
||||||
|
}
|
||||||
|
|
||||||
|
equals(other: Point): boolean {
|
||||||
|
if (!(other instanceof Point)) throw new TypeError('Point#equals: expected Point');
|
||||||
|
return this.x === other.x && this.y === other.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
negate(): Point {
|
||||||
|
return new Point(modP(-this.x), this.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
double(): Point {
|
||||||
|
return ExtendedPoint.fromAffine(this).double().toAffine();
|
||||||
|
}
|
||||||
|
|
||||||
|
add(other: Point) {
|
||||||
|
return ExtendedPoint.fromAffine(this).add(ExtendedPoint.fromAffine(other)).toAffine();
|
||||||
|
}
|
||||||
|
|
||||||
|
subtract(other: Point) {
|
||||||
|
return this.add(other.negate());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant time multiplication.
|
||||||
|
* @param scalar Big-Endian number
|
||||||
|
* @returns new point
|
||||||
|
*/
|
||||||
|
multiply(scalar: number | bigint): Point {
|
||||||
|
return ExtendedPoint.fromAffine(this).multiply(scalar, this).toAffine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EDDSA signature.
|
||||||
|
*/
|
||||||
|
class Signature implements SignatureType {
|
||||||
|
constructor(readonly r: Point, readonly s: bigint) {
|
||||||
|
this.assertValidity();
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromHex(hex: Hex) {
|
||||||
|
const bytes = ensureBytes(hex, 2 * fieldLen);
|
||||||
|
const r = Point.fromHex(bytes.slice(0, fieldLen), false);
|
||||||
|
const s = bytesToNumberLE(bytes.slice(fieldLen, 2 * fieldLen));
|
||||||
|
return new Signature(r, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertValidity() {
|
||||||
|
const { r, s } = this;
|
||||||
|
if (!(r instanceof Point)) throw new Error('Expected Point instance');
|
||||||
|
// 0 <= s < l
|
||||||
|
normalizeScalar(s, CURVE_ORDER, false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
toRawBytes() {
|
||||||
|
return concatBytes(this.r.toRawBytes(), numberToBytesLE(this.s, fieldLen));
|
||||||
|
}
|
||||||
|
|
||||||
|
toHex() {
|
||||||
|
return bytesToHex(this.toRawBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Little-endian SHA512 with modulo n
|
||||||
|
function modlLE(hash: Uint8Array): bigint {
|
||||||
|
return mod.mod(bytesToNumberLE(hash), CURVE_ORDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for num to be in range:
|
||||||
|
* For strict == true: `0 < num < max`.
|
||||||
|
* For strict == false: `0 <= num < max`.
|
||||||
|
* Converts non-float safe numbers to bigints.
|
||||||
|
*/
|
||||||
|
function normalizeScalar(num: number | bigint, max: bigint, strict = true): bigint {
|
||||||
|
if (!max) throw new TypeError('Specify max value');
|
||||||
|
if (typeof num === 'number' && Number.isSafeInteger(num)) num = BigInt(num);
|
||||||
|
if (typeof num === 'bigint' && num < max) {
|
||||||
|
if (strict) {
|
||||||
|
if (_0n < num) return num;
|
||||||
|
} else {
|
||||||
|
if (_0n <= num) return num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new TypeError('Expected valid scalar: 0 < scalar < max');
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkPrivateKey(key: PrivKey) {
|
||||||
|
// Normalize bigint / number / string to Uint8Array
|
||||||
|
key =
|
||||||
|
typeof key === 'bigint' || typeof key === 'number'
|
||||||
|
? numberToBytesLE(normalizeScalar(key, maxGroupElement), groupLen)
|
||||||
|
: ensureBytes(key);
|
||||||
|
if (key.length !== groupLen) throw new Error(`Expected ${groupLen} bytes, got ${key.length}`);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes 64 bytes
|
||||||
|
function getKeyFromHash(hashed: Uint8Array) {
|
||||||
|
// First 32 bytes of 64b uniformingly random input are taken,
|
||||||
|
// clears 3 bits of it to produce a random field element.
|
||||||
|
const head = adjustScalarBytes(hashed.slice(0, groupLen));
|
||||||
|
// Second 32 bytes is called key prefix (5.1.6)
|
||||||
|
const prefix = hashed.slice(groupLen, 2 * groupLen);
|
||||||
|
// The actual private scalar
|
||||||
|
const scalar = modlLE(head);
|
||||||
|
// Point on Edwards curve aka public key
|
||||||
|
const point = Point.BASE.multiply(scalar);
|
||||||
|
const pointBytes = point.toRawBytes();
|
||||||
|
return { head, prefix, scalar, point, pointBytes };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convenience method that creates public key and other stuff. RFC8032 5.1.5 */
|
||||||
|
function getExtendedPublicKey(key: PrivKey) {
|
||||||
|
return getKeyFromHash(CURVE.hash(checkPrivateKey(key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates ed25519 public key. RFC8032 5.1.5
|
||||||
|
* 1. private key is hashed with sha512, then first 32 bytes are taken from the hash
|
||||||
|
* 2. 3 least significant bits of the first byte are cleared
|
||||||
|
*/
|
||||||
|
function getPublicKey(privateKey: PrivKey): Uint8Array {
|
||||||
|
return getExtendedPublicKey(privateKey).pointBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMPTY = new Uint8Array();
|
||||||
|
function hashDomainToScalar(message: Uint8Array, context: Hex = EMPTY) {
|
||||||
|
context = ensureBytes(context);
|
||||||
|
return modlLE(CURVE.hash(domain(message, context, !!CURVE.preHash)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Signs message with privateKey. RFC8032 5.1.6 */
|
||||||
|
function sign(message: Hex, privateKey: Hex, context?: Hex): Uint8Array {
|
||||||
|
message = ensureBytes(message);
|
||||||
|
if (CURVE.preHash) message = CURVE.preHash(message);
|
||||||
|
const { prefix, scalar, pointBytes } = getExtendedPublicKey(privateKey);
|
||||||
|
const r = hashDomainToScalar(concatBytes(prefix, message), context);
|
||||||
|
const R = Point.BASE.multiply(r); // R = rG
|
||||||
|
const k = hashDomainToScalar(concatBytes(R.toRawBytes(), pointBytes, message), context); // k = hash(R+P+msg)
|
||||||
|
const s = mod.mod(r + k * scalar, CURVE_ORDER); // s = r + kp
|
||||||
|
return new Signature(R, s).toRawBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies EdDSA signature against message and public key.
|
||||||
|
* An extended group equation is checked.
|
||||||
|
* RFC8032 5.1.7
|
||||||
|
* Compliant with ZIP215:
|
||||||
|
* 0 <= sig.R/publicKey < 2**256 (can be >= curve.P)
|
||||||
|
* 0 <= sig.s < l
|
||||||
|
* Not compliant with RFC8032: it's not possible to comply to both ZIP & RFC at the same time.
|
||||||
|
*/
|
||||||
|
function verify(sig: SigType, message: Hex, publicKey: PubKey, context?: Hex): boolean {
|
||||||
|
message = ensureBytes(message);
|
||||||
|
if (CURVE.preHash) message = CURVE.preHash(message);
|
||||||
|
// When hex is passed, we check public key fully.
|
||||||
|
// When Point instance is passed, we assume it has already been checked, for performance.
|
||||||
|
// If user passes Point/Sig instance, we assume it has been already verified.
|
||||||
|
// We don't check its equations for performance. We do check for valid bounds for s though
|
||||||
|
// We always check for: a) s bounds. b) hex validity
|
||||||
|
if (publicKey instanceof Point) {
|
||||||
|
// ignore
|
||||||
|
} else if (publicKey instanceof Uint8Array || typeof publicKey === 'string') {
|
||||||
|
publicKey = Point.fromHex(publicKey, false);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Invalid publicKey: ${publicKey}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sig instanceof Signature) sig.assertValidity();
|
||||||
|
else if (sig instanceof Uint8Array || typeof sig === 'string') sig = Signature.fromHex(sig);
|
||||||
|
else throw new Error(`Wrong signature: ${sig}`);
|
||||||
|
|
||||||
|
const { r, s } = sig;
|
||||||
|
const SB = ExtendedPoint.BASE.multiplyUnsafe(s);
|
||||||
|
const k = hashDomainToScalar(
|
||||||
|
concatBytes(r.toRawBytes(), publicKey.toRawBytes(), message),
|
||||||
|
context
|
||||||
|
);
|
||||||
|
const kA = ExtendedPoint.fromAffine(publicKey).multiplyUnsafe(k);
|
||||||
|
const RkA = ExtendedPoint.fromAffine(r).add(kA);
|
||||||
|
// [8][S]B = [8]R + [8][k]A'
|
||||||
|
return RkA.subtract(SB).multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable precomputes. Slows down first publicKey computation by 20ms.
|
||||||
|
Point.BASE._setWindowSize(8);
|
||||||
|
|
||||||
|
const utils = {
|
||||||
|
getExtendedPublicKey,
|
||||||
|
mod: modP,
|
||||||
|
invert: (a: bigint, m = CURVE.P) => mod.invert(a, m),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not needed for ed25519 private keys. Needed if you use scalars directly (rare).
|
||||||
|
*/
|
||||||
|
hashToPrivateScalar: (hash: Hex): bigint => hashToPrivateScalar(hash, CURVE_ORDER, true),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ed25519 private keys are uniform 32-bit strings. We do not need to check for
|
||||||
|
* modulo bias like we do in secp256k1 randomPrivateKey()
|
||||||
|
*/
|
||||||
|
randomPrivateKey: (): Uint8Array => randomBytes(fieldLen),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We're doing scalar multiplication (used in getPublicKey etc) with precomputed BASE_POINT
|
||||||
|
* values. This slows down first getPublicKey() by milliseconds (see Speed section),
|
||||||
|
* but allows to speed-up subsequent getPublicKey() calls up to 20x.
|
||||||
|
* @param windowSize 2, 4, 8, 16
|
||||||
|
*/
|
||||||
|
precompute(windowSize = 8, point = Point.BASE): Point {
|
||||||
|
const cached = point.equals(Point.BASE) ? point : new Point(point.x, point.y);
|
||||||
|
cached._setWindowSize(windowSize);
|
||||||
|
cached.multiply(_2n);
|
||||||
|
return cached;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
CURVE,
|
||||||
|
getPublicKey,
|
||||||
|
sign,
|
||||||
|
verify,
|
||||||
|
ExtendedPoint,
|
||||||
|
Point,
|
||||||
|
Signature,
|
||||||
|
utils,
|
||||||
|
};
|
||||||
|
}
|
||||||
127
src/group.ts
Normal file
127
src/group.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
// Default group related functions
|
||||||
|
const _0n = BigInt(0);
|
||||||
|
const _1n = BigInt(1);
|
||||||
|
|
||||||
|
export interface Group<T extends Group<T>> {
|
||||||
|
double(): T;
|
||||||
|
negate(): T;
|
||||||
|
add(other: T): T;
|
||||||
|
subtract(other: T): T;
|
||||||
|
equals(other: T): boolean;
|
||||||
|
multiply(scalar: number | bigint): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GroupConstructor<T> = {
|
||||||
|
BASE: T;
|
||||||
|
ZERO: T;
|
||||||
|
};
|
||||||
|
// Not big, but pretty complex and it is easy to break stuff. To avoid too much copy paste
|
||||||
|
export function wNAF<T extends Group<T>>(c: GroupConstructor<T>, bits: number) {
|
||||||
|
const constTimeNegate = (condition: boolean, item: T): T => {
|
||||||
|
const neg = item.negate();
|
||||||
|
return condition ? neg : item;
|
||||||
|
};
|
||||||
|
const opts = (W: number) => {
|
||||||
|
if (256 % W) throw new Error('Invalid precomputation window, must be power of 2');
|
||||||
|
const windows = Math.ceil(bits / W) + 1; // +1, because
|
||||||
|
const windowSize = 2 ** (W - 1); // -1 because we skip zero
|
||||||
|
return { windows, windowSize };
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
constTimeNegate,
|
||||||
|
// non-const time multiplication ladder
|
||||||
|
unsafeLadder(elm: T, n: bigint) {
|
||||||
|
let p = c.ZERO;
|
||||||
|
let d: T = elm;
|
||||||
|
while (n > _0n) {
|
||||||
|
if (n & _1n) p = p.add(d);
|
||||||
|
d = d.double();
|
||||||
|
n >>= _1n;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a wNAF precomputation window. Used for caching.
|
||||||
|
* Default window size is set by `utils.precompute()` and is equal to 8.
|
||||||
|
* Which means we are caching 65536 points: 256 points for every bit from 0 to 256.
|
||||||
|
* @returns 65K precomputed points, depending on W
|
||||||
|
*/
|
||||||
|
precomputeWindow(elm: T, W: number): Group<T>[] {
|
||||||
|
const { windows, windowSize } = opts(W);
|
||||||
|
const points: T[] = [];
|
||||||
|
let p: T = elm;
|
||||||
|
let base = p;
|
||||||
|
for (let window = 0; window < windows; window++) {
|
||||||
|
base = p;
|
||||||
|
points.push(base);
|
||||||
|
// =1, because we skip zero
|
||||||
|
for (let i = 1; i < windowSize; i++) {
|
||||||
|
base = base.add(p);
|
||||||
|
points.push(base);
|
||||||
|
}
|
||||||
|
p = base.double();
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements w-ary non-adjacent form for calculating ec multiplication.
|
||||||
|
* @param n
|
||||||
|
* @param affinePoint optional 2d point to save cached precompute windows on it.
|
||||||
|
* @returns real and fake (for const-time) points
|
||||||
|
*/
|
||||||
|
wNAF(W: number, precomputes: T[], n: bigint): { p: T; f: T } {
|
||||||
|
const { windows, windowSize } = opts(W);
|
||||||
|
|
||||||
|
let p = c.ZERO;
|
||||||
|
let f = c.BASE;
|
||||||
|
|
||||||
|
const mask = BigInt(2 ** W - 1); // Create mask with W ones: 0b1111 for W=4 etc.
|
||||||
|
const maxNumber = 2 ** W;
|
||||||
|
const shiftBy = BigInt(W);
|
||||||
|
|
||||||
|
for (let window = 0; window < windows; window++) {
|
||||||
|
const offset = window * windowSize;
|
||||||
|
// Extract W bits.
|
||||||
|
let wbits = Number(n & mask);
|
||||||
|
|
||||||
|
// Shift number by W bits.
|
||||||
|
n >>= shiftBy;
|
||||||
|
|
||||||
|
// If the bits are bigger than max size, we'll split those.
|
||||||
|
// +224 => 256 - 32
|
||||||
|
if (wbits > windowSize) {
|
||||||
|
wbits -= maxNumber;
|
||||||
|
n += _1n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This code was first written with assumption that 'f' and 'p' will never be infinity point:
|
||||||
|
// since each addition is multiplied by 2 ** W, it cannot cancel each other. However,
|
||||||
|
// there is negate now: it is possible that negated element from low value
|
||||||
|
// would be the same as high element, which will create carry into next window.
|
||||||
|
// It's not obvious how this can fail, but still worth investigating later.
|
||||||
|
|
||||||
|
// Check if we're onto Zero point.
|
||||||
|
// Add random point inside current window to f.
|
||||||
|
const offset1 = offset;
|
||||||
|
const offset2 = offset + Math.abs(wbits) - 1; // -1 because we skip zero
|
||||||
|
const cond1 = window % 2 !== 0;
|
||||||
|
const cond2 = wbits < 0;
|
||||||
|
if (wbits === 0) {
|
||||||
|
// The most important part for const-time getPublicKey
|
||||||
|
f = f.add(constTimeNegate(cond1, precomputes[offset1]));
|
||||||
|
} else {
|
||||||
|
p = p.add(constTimeNegate(cond2, precomputes[offset2]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// JIT-compiler should not eliminate f here, since it will later be used in normalizeZ()
|
||||||
|
// Even if the variable is still unused, there are some checks which will
|
||||||
|
// throw an exception, so compiler needs to prove they won't happen, which is hard.
|
||||||
|
// At this point there is a way to F be infinity-point even if p is not,
|
||||||
|
// which makes it less const-time: around 1 bigint multiply.
|
||||||
|
return { p, f };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
|
|
||||||
|
// Utilities for modular arithmetics
|
||||||
const _0n = BigInt(0);
|
const _0n = BigInt(0);
|
||||||
const _1n = BigInt(1);
|
const _1n = BigInt(1);
|
||||||
const _2n = BigInt(2);
|
const _2n = BigInt(2);
|
||||||
@@ -97,7 +99,6 @@ export function legendre(num: bigint, fieldPrime: bigint): bigint {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates square root of a number in a finite field.
|
* Calculates square root of a number in a finite field.
|
||||||
* Used to calculate y - the square root of y².
|
|
||||||
*/
|
*/
|
||||||
export function sqrt(number: bigint, modulo: bigint): bigint {
|
export function sqrt(number: bigint, modulo: bigint): bigint {
|
||||||
const n = number;
|
const n = number;
|
||||||
@@ -107,6 +108,7 @@ export function sqrt(number: bigint, modulo: bigint): bigint {
|
|||||||
// P = 3 (mod 4)
|
// P = 3 (mod 4)
|
||||||
// sqrt n = n^((P+1)/4)
|
// sqrt n = n^((P+1)/4)
|
||||||
if (P % _4n === _3n) return pow(n, p1div4, P);
|
if (P % _4n === _3n) return pow(n, p1div4, P);
|
||||||
|
|
||||||
// P = 5 (mod 8)
|
// P = 5 (mod 8)
|
||||||
if (P % _8n === _5n) {
|
if (P % _8n === _5n) {
|
||||||
const n2 = mod(n * _2n, P);
|
const n2 = mod(n * _2n, P);
|
||||||
@@ -144,3 +146,14 @@ export function sqrt(number: bigint, modulo: bigint): bigint {
|
|||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Little-endian check for first LE bit (last BE bit);
|
||||||
|
export const isNegativeLE = (num: bigint, modulo: bigint) => (mod(num, modulo) & _1n) === _1n;
|
||||||
|
|
||||||
|
// An idea on modular arithmetic for bls12-381:
|
||||||
|
// const FIELD = {add, pow, sqrt, mul};
|
||||||
|
// Functions will take field elements, no need for an additional class
|
||||||
|
// Could be faster. 1 bigint field will just do operations and mod later:
|
||||||
|
// instead of 'r = mod(r * b, P)' we will write r = mul(r, b);
|
||||||
|
// Could be insecure without shape check, so it needs to be done.
|
||||||
|
// Functions could be inlined by JIT.
|
||||||
|
|||||||
213
src/montgomery.ts
Normal file
213
src/montgomery.ts
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import * as mod from './modular.js';
|
||||||
|
import {
|
||||||
|
ensureBytes,
|
||||||
|
numberToBytesLE,
|
||||||
|
bytesToNumberLE,
|
||||||
|
// nLength,
|
||||||
|
} from './utils.js';
|
||||||
|
|
||||||
|
const _0n = BigInt(0);
|
||||||
|
const _1n = BigInt(1);
|
||||||
|
type Hex = string | Uint8Array;
|
||||||
|
|
||||||
|
export type CurveType = {
|
||||||
|
// Field over which we'll do calculations. Verify with:
|
||||||
|
P: bigint;
|
||||||
|
nByteLength: number;
|
||||||
|
adjustScalarBytes?: (bytes: Uint8Array) => Uint8Array;
|
||||||
|
domain?: (data: Uint8Array, ctx: Uint8Array, phflag: boolean) => Uint8Array;
|
||||||
|
a24: bigint; // Related to d, but cannot be derived from it
|
||||||
|
montgomeryBits: number;
|
||||||
|
powPminus2?: (x: bigint) => bigint;
|
||||||
|
xyToU?: (x: bigint, y: bigint) => bigint;
|
||||||
|
Gu: string;
|
||||||
|
};
|
||||||
|
export type CurveFn = {
|
||||||
|
scalarMult: (u: Hex, scalar: Hex) => Uint8Array;
|
||||||
|
scalarMultBase: (scalar: Hex) => Uint8Array;
|
||||||
|
getPublicKey: (privateKey: Hex) => Uint8Array;
|
||||||
|
Gu: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function validateOpts(curve: CurveType) {
|
||||||
|
for (const i of ['a24'] as const) {
|
||||||
|
if (typeof curve[i] !== 'bigint')
|
||||||
|
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
||||||
|
}
|
||||||
|
for (const i of ['montgomeryBits', 'nByteLength'] as const) {
|
||||||
|
if (curve[i] === undefined) continue; // Optional
|
||||||
|
if (!Number.isSafeInteger(curve[i]))
|
||||||
|
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
||||||
|
}
|
||||||
|
for (const fn of ['adjustScalarBytes', 'domain', 'powPminus2'] as const) {
|
||||||
|
if (curve[fn] === undefined) continue; // Optional
|
||||||
|
if (typeof curve[fn] !== 'function') throw new Error(`Invalid ${fn} function`);
|
||||||
|
}
|
||||||
|
for (const i of ['Gu'] as const) {
|
||||||
|
if (curve[i] === undefined) continue; // Optional
|
||||||
|
if (typeof curve[i] !== 'string')
|
||||||
|
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
||||||
|
}
|
||||||
|
// Set defaults
|
||||||
|
// ...nLength(curve.n, curve.nBitLength),
|
||||||
|
return Object.freeze({ ...curve } as const);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: not really montgomery curve, just bunch of very specific methods for X25519/X448 (RFC 7748, https://www.rfc-editor.org/rfc/rfc7748)
|
||||||
|
// Uses only one coordinate instead of two
|
||||||
|
export function montgomery(curveDef: CurveType): CurveFn {
|
||||||
|
const CURVE = validateOpts(curveDef);
|
||||||
|
const { P } = CURVE;
|
||||||
|
const modP = (a: bigint) => mod.mod(a, P);
|
||||||
|
const montgomeryBits = CURVE.montgomeryBits;
|
||||||
|
const montgomeryBytes = Math.ceil(montgomeryBits / 8);
|
||||||
|
const fieldLen = CURVE.nByteLength;
|
||||||
|
const adjustScalarBytes = CURVE.adjustScalarBytes || ((bytes: Uint8Array) => bytes);
|
||||||
|
const powPminus2 = CURVE.powPminus2 || ((x: bigint) => mod.pow(x, P - BigInt(2), P));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for num to be in range:
|
||||||
|
* For strict == true: `0 < num < max`.
|
||||||
|
* For strict == false: `0 <= num < max`.
|
||||||
|
* Converts non-float safe numbers to bigints.
|
||||||
|
*/
|
||||||
|
function normalizeScalar(num: number | bigint, max: bigint, strict = true): bigint {
|
||||||
|
if (!max) throw new TypeError('Specify max value');
|
||||||
|
if (typeof num === 'number' && Number.isSafeInteger(num)) num = BigInt(num);
|
||||||
|
if (typeof num === 'bigint' && num < max) {
|
||||||
|
if (strict) {
|
||||||
|
if (_0n < num) return num;
|
||||||
|
} else {
|
||||||
|
if (_0n <= num) return num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new TypeError('Expected valid scalar: 0 < scalar < max');
|
||||||
|
}
|
||||||
|
|
||||||
|
// cswap from RFC7748
|
||||||
|
// NOTE: cswap is not from RFC7748!
|
||||||
|
/*
|
||||||
|
cswap(swap, x_2, x_3):
|
||||||
|
dummy = mask(swap) AND (x_2 XOR x_3)
|
||||||
|
x_2 = x_2 XOR dummy
|
||||||
|
x_3 = x_3 XOR dummy
|
||||||
|
Return (x_2, x_3)
|
||||||
|
Where mask(swap) is the all-1 or all-0 word of the same length as x_2
|
||||||
|
and x_3, computed, e.g., as mask(swap) = 0 - swap.
|
||||||
|
*/
|
||||||
|
function cswap(swap: bigint, x_2: bigint, x_3: bigint): [bigint, bigint] {
|
||||||
|
const dummy = modP(swap * (x_2 - x_3));
|
||||||
|
x_2 = modP(x_2 - dummy);
|
||||||
|
x_3 = modP(x_3 + dummy);
|
||||||
|
return [x_2, x_3];
|
||||||
|
}
|
||||||
|
|
||||||
|
// x25519 from 4
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param pointU u coordinate (x) on Montgomery Curve 25519
|
||||||
|
* @param scalar by which the point would be multiplied
|
||||||
|
* @returns new Point on Montgomery curve
|
||||||
|
*/
|
||||||
|
function montgomeryLadder(pointU: bigint, scalar: bigint): bigint {
|
||||||
|
const { P } = CURVE;
|
||||||
|
const u = normalizeScalar(pointU, P);
|
||||||
|
// Section 5: Implementations MUST accept non-canonical values and process them as
|
||||||
|
// if they had been reduced modulo the field prime.
|
||||||
|
const k = normalizeScalar(scalar, P);
|
||||||
|
// The constant a24 is (486662 - 2) / 4 = 121665 for curve25519/X25519
|
||||||
|
const a24 = CURVE.a24;
|
||||||
|
const x_1 = u;
|
||||||
|
let x_2 = _1n;
|
||||||
|
let z_2 = _0n;
|
||||||
|
let x_3 = u;
|
||||||
|
let z_3 = _1n;
|
||||||
|
let swap = _0n;
|
||||||
|
let sw: [bigint, bigint];
|
||||||
|
for (let t = BigInt(montgomeryBits - 1); t >= _0n; t--) {
|
||||||
|
const k_t = (k >> t) & _1n;
|
||||||
|
swap ^= k_t;
|
||||||
|
sw = cswap(swap, x_2, x_3);
|
||||||
|
x_2 = sw[0];
|
||||||
|
x_3 = sw[1];
|
||||||
|
sw = cswap(swap, z_2, z_3);
|
||||||
|
z_2 = sw[0];
|
||||||
|
z_3 = sw[1];
|
||||||
|
swap = k_t;
|
||||||
|
|
||||||
|
const A = x_2 + z_2;
|
||||||
|
const AA = modP(A * A);
|
||||||
|
const B = x_2 - z_2;
|
||||||
|
const BB = modP(B * B);
|
||||||
|
const E = AA - BB;
|
||||||
|
const C = x_3 + z_3;
|
||||||
|
const D = x_3 - z_3;
|
||||||
|
const DA = modP(D * A);
|
||||||
|
const CB = modP(C * B);
|
||||||
|
const dacb = DA + CB;
|
||||||
|
const da_cb = DA - CB;
|
||||||
|
x_3 = modP(dacb * dacb);
|
||||||
|
z_3 = modP(x_1 * modP(da_cb * da_cb));
|
||||||
|
x_2 = modP(AA * BB);
|
||||||
|
z_2 = modP(E * (AA + modP(a24 * E)));
|
||||||
|
}
|
||||||
|
// (x_2, x_3) = cswap(swap, x_2, x_3)
|
||||||
|
sw = cswap(swap, x_2, x_3);
|
||||||
|
x_2 = sw[0];
|
||||||
|
x_3 = sw[1];
|
||||||
|
// (z_2, z_3) = cswap(swap, z_2, z_3)
|
||||||
|
sw = cswap(swap, z_2, z_3);
|
||||||
|
z_2 = sw[0];
|
||||||
|
z_3 = sw[1];
|
||||||
|
// z_2^(p - 2)
|
||||||
|
const z2 = powPminus2(z_2);
|
||||||
|
// Return x_2 * (z_2^(p - 2))
|
||||||
|
return modP(x_2 * z2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeUCoordinate(u: bigint): Uint8Array {
|
||||||
|
return numberToBytesLE(modP(u), montgomeryBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeUCoordinate(uEnc: Hex): bigint {
|
||||||
|
const u = ensureBytes(uEnc, montgomeryBytes);
|
||||||
|
// Section 5: When receiving such an array, implementations of X25519
|
||||||
|
// MUST mask the most significant bit in the final byte.
|
||||||
|
// This is very ugly way, but it works because fieldLen-1 is outside of bounds for X448, so this becomes NOOP
|
||||||
|
// fieldLen - scalaryBytes = 1 for X448 and = 0 for X25519
|
||||||
|
u[fieldLen - 1] &= 127; // 0b0111_1111
|
||||||
|
return bytesToNumberLE(u);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeScalar(n: Hex): bigint {
|
||||||
|
const bytes = ensureBytes(n);
|
||||||
|
if (bytes.length !== montgomeryBytes && bytes.length !== fieldLen)
|
||||||
|
throw new Error(`Expected ${montgomeryBytes} or ${fieldLen} bytes, got ${bytes.length}`);
|
||||||
|
return bytesToNumberLE(adjustScalarBytes(bytes));
|
||||||
|
}
|
||||||
|
// Multiply point u by scalar
|
||||||
|
function scalarMult(u: Hex, scalar: Hex): Uint8Array {
|
||||||
|
const pointU = decodeUCoordinate(u);
|
||||||
|
const _scalar = decodeScalar(scalar);
|
||||||
|
const pu = montgomeryLadder(pointU, _scalar);
|
||||||
|
// The result was not contributory
|
||||||
|
// https://cr.yp.to/ecdh.html#validate
|
||||||
|
if (pu === _0n) throw new Error('Invalid private or public key received');
|
||||||
|
return encodeUCoordinate(pu);
|
||||||
|
}
|
||||||
|
// Multiply base point by scalar
|
||||||
|
function scalarMultBase(scalar: Hex): Uint8Array {
|
||||||
|
return scalarMult(CURVE.Gu, scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// NOTE: we can get 'y' coordinate from 'u', but Point.fromHex also wants 'x' coordinate oddity flag, and we cannot get 'x' without knowing 'v'
|
||||||
|
// Need to add generic conversion between twisted edwards and complimentary curve for JubJub
|
||||||
|
scalarMult,
|
||||||
|
scalarMultBase,
|
||||||
|
// NOTE: these function work on complimentary montgomery curve
|
||||||
|
// getSharedSecret: (privateKey: Hex, publicKey: Hex) => scalarMult(publicKey, privateKey),
|
||||||
|
getPublicKey: (privateKey: Hex): Uint8Array => scalarMultBase(privateKey),
|
||||||
|
Gu: CURVE.Gu,
|
||||||
|
};
|
||||||
|
}
|
||||||
93
src/utils.ts
93
src/utils.ts
@@ -1,6 +1,44 @@
|
|||||||
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! @noble/curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
// Convert between types
|
|
||||||
// ---------------------
|
// We accept hex strings besides Uint8Array for simplicity
|
||||||
|
export type Hex = Uint8Array | string;
|
||||||
|
// Very few implementations accept numbers, we do it to ease learning curve
|
||||||
|
export type PrivKey = Hex | bigint | number;
|
||||||
|
|
||||||
|
// NOTE: these are generic, even if curve is on some polynominal field (bls), it will still have P/n/h
|
||||||
|
// But generator can be different (Fp2/Fp6 for bls?)
|
||||||
|
export type BasicCurve = {
|
||||||
|
// Field over which we'll do calculations (Fp)
|
||||||
|
P: bigint;
|
||||||
|
// Curve order, total count of valid points in the field
|
||||||
|
n: bigint;
|
||||||
|
// Bit/byte length of curve order
|
||||||
|
nBitLength?: number;
|
||||||
|
nByteLength?: number;
|
||||||
|
// Cofactor
|
||||||
|
// NOTE: we can assign default value of 1, but then users will just ignore it, without validating with spec
|
||||||
|
// Has not use for now, but nice to have in API
|
||||||
|
h: bigint;
|
||||||
|
// Base point (x, y) aka generator point
|
||||||
|
Gx: bigint;
|
||||||
|
Gy: bigint;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function validateOpts<T extends BasicCurve>(curve: T) {
|
||||||
|
for (const i of ['P', 'n', 'h', 'Gx', 'Gy'] as const) {
|
||||||
|
if (typeof curve[i] !== 'bigint')
|
||||||
|
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
||||||
|
}
|
||||||
|
for (const i of ['nBitLength', 'nByteLength'] as const) {
|
||||||
|
if (curve[i] === undefined) continue; // Optional
|
||||||
|
if (!Number.isSafeInteger(curve[i]))
|
||||||
|
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
||||||
|
}
|
||||||
|
// Set defaults
|
||||||
|
return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve } as const);
|
||||||
|
}
|
||||||
|
|
||||||
|
import * as mod from './modular.js';
|
||||||
|
|
||||||
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
|
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
|
||||||
export function bytesToHex(uint8a: Uint8Array): string {
|
export function bytesToHex(uint8a: Uint8Array): string {
|
||||||
@@ -44,14 +82,25 @@ export function hexToBytes(hex: string): Uint8Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Big Endian
|
// Big Endian
|
||||||
export function bytesToNumber(bytes: Uint8Array): bigint {
|
export function bytesToNumberBE(bytes: Uint8Array): bigint {
|
||||||
return hexToNumber(bytesToHex(bytes));
|
return hexToNumber(bytesToHex(bytes));
|
||||||
}
|
}
|
||||||
|
export function bytesToNumberLE(uint8a: Uint8Array): bigint {
|
||||||
|
if (!(uint8a instanceof Uint8Array)) throw new Error('Expected Uint8Array');
|
||||||
|
return BigInt('0x' + bytesToHex(Uint8Array.from(uint8a).reverse()));
|
||||||
|
}
|
||||||
|
|
||||||
export function ensureBytes(hex: string | Uint8Array): Uint8Array {
|
export const numberToBytesBE = (n: bigint, len: number) =>
|
||||||
|
hexToBytes(n.toString(16).padStart(len * 2, '0'));
|
||||||
|
export const numberToBytesLE = (n: bigint, len: number) => numberToBytesBE(n, len).reverse();
|
||||||
|
|
||||||
|
export function ensureBytes(hex: Hex, expectedLength?: number): Uint8Array {
|
||||||
// Uint8Array.from() instead of hash.slice() because node.js Buffer
|
// Uint8Array.from() instead of hash.slice() because node.js Buffer
|
||||||
// is instance of Uint8Array, and its slice() creates **mutable** copy
|
// is instance of Uint8Array, and its slice() creates **mutable** copy
|
||||||
return hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes(hex);
|
const bytes = hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes(hex);
|
||||||
|
if (typeof expectedLength === 'number' && bytes.length !== expectedLength)
|
||||||
|
throw new Error(`Expected ${expectedLength} bytes`);
|
||||||
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copies several Uint8Arrays into one.
|
// Copies several Uint8Arrays into one.
|
||||||
@@ -67,3 +116,37 @@ export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CURVE.n lengths
|
||||||
|
export function nLength(n: bigint, nBitLength?: number) {
|
||||||
|
// Bit size, byte size of CURVE.n
|
||||||
|
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;
|
||||||
|
const nByteLength = Math.ceil(_nBitLength / 8);
|
||||||
|
return { nBitLength: _nBitLength, nByteLength };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can take (n+8) or more bytes of uniform input e.g. from CSPRNG or KDF
|
||||||
|
* and convert them into private scalar, with the modulo bias being neglible.
|
||||||
|
* As per FIPS 186 B.4.1.
|
||||||
|
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
||||||
|
* @param hash hash output from sha512, or a similar function
|
||||||
|
* @returns valid private scalar
|
||||||
|
*/
|
||||||
|
const _1n = BigInt(1);
|
||||||
|
export function hashToPrivateScalar(hash: Hex, CURVE_ORDER: bigint, isLE = false): bigint {
|
||||||
|
hash = ensureBytes(hash);
|
||||||
|
const orderLen = nLength(CURVE_ORDER).nByteLength;
|
||||||
|
const minLen = orderLen + 8;
|
||||||
|
if (orderLen < 16 || hash.length < minLen || hash.length > 1024)
|
||||||
|
throw new Error('Expected valid bytes of private key as per FIPS 186');
|
||||||
|
const num = isLE ? bytesToNumberLE(hash) : bytesToNumberBE(hash);
|
||||||
|
return mod.mod(num, CURVE_ORDER - _1n) + _1n;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function equalBytes(b1: Uint8Array, b2: Uint8Array) {
|
||||||
|
// We don't care about timing attacks here
|
||||||
|
if (b1.length !== b2.length) return false;
|
||||||
|
for (let i = 0; i < b1.length; i++) if (b1[i] !== b2[i]) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,30 @@
|
|||||||
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
||||||
// Implementation of Short weierstrass curve. The formula is: y² = x³ + ax + b
|
// Implementation of Short Weierstrass curve. The formula is: y² = x³ + ax + b
|
||||||
|
|
||||||
// TODO: sync vs async naming
|
// TODO: sync vs async naming
|
||||||
// TODO: default randomBytes
|
// TODO: default randomBytes
|
||||||
|
// Differences from @noble/secp256k1 1.7:
|
||||||
|
// 1. Different double() formula (but same addition)
|
||||||
|
// 2. Different sqrt() function
|
||||||
|
// 3. truncateHash() truncateOnly mode
|
||||||
|
// 4. DRBG supports outputLen bigger than outputLen of hmac
|
||||||
|
|
||||||
import * as mod from './modular.js';
|
import * as mod from './modular.js';
|
||||||
import {
|
import {
|
||||||
bytesToHex,
|
bytesToHex,
|
||||||
bytesToNumber,
|
bytesToNumberBE,
|
||||||
concatBytes,
|
concatBytes,
|
||||||
ensureBytes,
|
ensureBytes,
|
||||||
hexToBytes,
|
hexToBytes,
|
||||||
hexToNumber,
|
hexToNumber,
|
||||||
numberToHexUnpadded,
|
numberToHexUnpadded,
|
||||||
|
hashToPrivateScalar,
|
||||||
|
BasicCurve,
|
||||||
|
validateOpts as utilOpts,
|
||||||
|
Hex,
|
||||||
|
PrivKey,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
|
import { Group, GroupConstructor, wNAF } from './group.js';
|
||||||
|
|
||||||
export type CHash = {
|
export type CHash = {
|
||||||
(message: Uint8Array | string): Uint8Array;
|
(message: Uint8Array | string): Uint8Array;
|
||||||
@@ -26,61 +38,39 @@ type EndomorphismOpts = {
|
|||||||
splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
|
splitScalar: (k: bigint) => { k1neg: boolean; k1: bigint; k2neg: boolean; k2: bigint };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CurveType = {
|
export type CurveType = BasicCurve & {
|
||||||
// Params: a, b
|
// Params: a, b
|
||||||
a: bigint;
|
a: bigint;
|
||||||
b: bigint;
|
b: bigint;
|
||||||
// Field over which we'll do calculations. Verify with:
|
|
||||||
P: bigint;
|
|
||||||
// Curve order, total count of valid points in the field. Verify with:
|
|
||||||
n: bigint;
|
|
||||||
nBitLength?: number;
|
|
||||||
nByteLength?: number;
|
|
||||||
// Base point (x, y) aka generator point
|
|
||||||
Gx: bigint;
|
|
||||||
Gy: bigint;
|
|
||||||
|
|
||||||
// Default options
|
// Default options
|
||||||
lowS?: boolean;
|
lowS?: boolean;
|
||||||
|
|
||||||
// Hashes
|
// Hashes
|
||||||
hash: CHash; // Because we need outputLen for DRBG
|
hash: CHash; // Because we need outputLen for DRBG
|
||||||
hmac: HmacFnSync;
|
hmac: HmacFnSync;
|
||||||
randomBytes: (bytesLength?: number) => Uint8Array;
|
randomBytes: (bytesLength?: number) => Uint8Array;
|
||||||
|
|
||||||
truncateHash?: (hash: Uint8Array, truncateOnly?: boolean) => bigint;
|
truncateHash?: (hash: Uint8Array, truncateOnly?: boolean) => bigint;
|
||||||
// Some fields can have specialized fast case
|
// Some fields can have specialized fast case
|
||||||
sqrtMod?: (n: bigint) => bigint;
|
sqrtMod?: (n: bigint) => bigint;
|
||||||
|
|
||||||
// TODO: move into options?
|
// TODO: move into options?
|
||||||
// Endomorphism options for Koblitz curves
|
// Endomorphism options for Koblitz curves
|
||||||
endo?: EndomorphismOpts;
|
endo?: EndomorphismOpts;
|
||||||
};
|
};
|
||||||
|
|
||||||
// We accept hex strings besides Uint8Array for simplicity
|
|
||||||
type Hex = Uint8Array | string;
|
|
||||||
// Very few implementations accept numbers, we do it to ease learning curve
|
|
||||||
type PrivKey = Hex | bigint | number;
|
|
||||||
|
|
||||||
// Should be separate from overrides, since overrides can use information about curve (for example nBits)
|
// Should be separate from overrides, since overrides can use information about curve (for example nBits)
|
||||||
function validateOpts(curve: CurveType) {
|
function validateOpts(curve: CurveType) {
|
||||||
if (typeof curve.hash !== 'function' || !Number.isSafeInteger(curve.hash.outputLen))
|
const opts = utilOpts(curve);
|
||||||
|
if (typeof opts.hash !== 'function' || !Number.isSafeInteger(opts.hash.outputLen))
|
||||||
throw new Error('Invalid hash function');
|
throw new Error('Invalid hash function');
|
||||||
if (typeof curve.hmac !== 'function') throw new Error('Invalid hmac function');
|
if (typeof opts.hmac !== 'function') throw new Error('Invalid hmac function');
|
||||||
if (typeof curve.randomBytes !== 'function') throw new Error('Invalid randomBytes function');
|
if (typeof opts.randomBytes !== 'function') throw new Error('Invalid randomBytes function');
|
||||||
|
|
||||||
for (const i of ['a', 'b', 'P', 'n', 'Gx', 'Gy'] as const) {
|
for (const i of ['a', 'b'] as const) {
|
||||||
if (typeof curve[i] !== 'bigint')
|
if (typeof opts[i] !== 'bigint')
|
||||||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
throw new Error(`Invalid curve param ${i}=${opts[i]} (${typeof opts[i]})`);
|
||||||
}
|
}
|
||||||
for (const i of ['nBitLength', 'nByteLength'] as const) {
|
const endo = opts.endo;
|
||||||
if (curve[i] === undefined) continue; // Optional
|
|
||||||
if (!Number.isSafeInteger(curve[i]))
|
|
||||||
throw new Error(`Invalid curve param ${i}=${curve[i]} (${typeof curve[i]})`);
|
|
||||||
}
|
|
||||||
const endo = curve.endo;
|
|
||||||
if (endo) {
|
if (endo) {
|
||||||
if (curve.a !== _0n) {
|
if (opts.a !== _0n) {
|
||||||
throw new Error('Endomorphism can only be defined for Koblitz curves that have a=0');
|
throw new Error('Endomorphism can only be defined for Koblitz curves that have a=0');
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -91,10 +81,8 @@ function validateOpts(curve: CurveType) {
|
|||||||
throw new Error('Expected endomorphism with beta: bigint and splitScalar: function');
|
throw new Error('Expected endomorphism with beta: bigint and splitScalar: function');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const nBitLength = curve.n.toString(2).length; // Bit size of CURVE.n
|
|
||||||
const nByteLength = Math.ceil(nBitLength / 8); // Byte size of CURVE.n
|
|
||||||
// Set defaults
|
// Set defaults
|
||||||
return Object.freeze({ lowS: true, nBitLength, nByteLength, ...curve } as const);
|
return Object.freeze({ lowS: true, ...opts } as const);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: convert bits to bytes aligned to 32 bits? (224 for example)
|
// TODO: convert bits to bytes aligned to 32 bits? (224 for example)
|
||||||
@@ -125,7 +113,7 @@ function parseDERInt(data: Uint8Array) {
|
|||||||
if (res[0] === 0x00 && res[1] <= 0x7f) {
|
if (res[0] === 0x00 && res[1] <= 0x7f) {
|
||||||
throw new DERError('Invalid signature integer: trailing length');
|
throw new DERError('Invalid signature integer: trailing length');
|
||||||
}
|
}
|
||||||
return { data: bytesToNumber(res), left: data.subarray(len + 2) };
|
return { data: bytesToNumberBE(res), left: data.subarray(len + 2) };
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDERSignature(data: Uint8Array) {
|
function parseDERSignature(data: Uint8Array) {
|
||||||
@@ -199,30 +187,23 @@ export type SignatureConstructor = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Instance
|
// Instance
|
||||||
export interface JacobianPointType {
|
export interface JacobianPointType extends Group<JacobianPointType> {
|
||||||
readonly x: bigint;
|
readonly x: bigint;
|
||||||
readonly y: bigint;
|
readonly y: bigint;
|
||||||
readonly z: bigint;
|
readonly z: bigint;
|
||||||
equals(other: JacobianPointType): boolean;
|
|
||||||
negate(): JacobianPointType;
|
|
||||||
double(): JacobianPointType;
|
|
||||||
add(other: JacobianPointType): JacobianPointType;
|
|
||||||
subtract(other: JacobianPointType): JacobianPointType;
|
|
||||||
multiplyUnsafe(scalar: bigint): JacobianPointType;
|
|
||||||
multiply(scalar: number | bigint, affinePoint?: PointType): JacobianPointType;
|
multiply(scalar: number | bigint, affinePoint?: PointType): JacobianPointType;
|
||||||
|
multiplyUnsafe(scalar: bigint): JacobianPointType;
|
||||||
toAffine(invZ?: bigint): PointType;
|
toAffine(invZ?: bigint): PointType;
|
||||||
}
|
}
|
||||||
// Static methods
|
// Static methods
|
||||||
export type JacobianPointConstructor = {
|
export interface JacobianPointConstructor extends GroupConstructor<JacobianPointType> {
|
||||||
new (x: bigint, y: bigint, z: bigint): JacobianPointType;
|
new (x: bigint, y: bigint, z: bigint): JacobianPointType;
|
||||||
BASE: JacobianPointType;
|
|
||||||
ZERO: JacobianPointType;
|
|
||||||
fromAffine(p: PointType): JacobianPointType;
|
fromAffine(p: PointType): JacobianPointType;
|
||||||
toAffineBatch(points: JacobianPointType[]): PointType[];
|
toAffineBatch(points: JacobianPointType[]): PointType[];
|
||||||
normalizeZ(points: JacobianPointType[]): JacobianPointType[];
|
normalizeZ(points: JacobianPointType[]): JacobianPointType[];
|
||||||
};
|
}
|
||||||
// Instance
|
// Instance
|
||||||
export interface PointType {
|
export interface PointType extends Group<PointType> {
|
||||||
readonly x: bigint;
|
readonly x: bigint;
|
||||||
readonly y: bigint;
|
readonly y: bigint;
|
||||||
_setWindowSize(windowSize: number): void;
|
_setWindowSize(windowSize: number): void;
|
||||||
@@ -230,22 +211,14 @@ export interface PointType {
|
|||||||
toRawBytes(isCompressed?: boolean): Uint8Array;
|
toRawBytes(isCompressed?: boolean): Uint8Array;
|
||||||
toHex(isCompressed?: boolean): string;
|
toHex(isCompressed?: boolean): string;
|
||||||
assertValidity(): void;
|
assertValidity(): void;
|
||||||
equals(other: PointType): boolean;
|
|
||||||
negate(): PointType;
|
|
||||||
double(): PointType;
|
|
||||||
add(other: PointType): PointType;
|
|
||||||
subtract(other: PointType): PointType;
|
|
||||||
multiply(scalar: number | bigint): PointType;
|
|
||||||
multiplyAndAddUnsafe(Q: PointType, a: bigint, b: bigint): PointType | undefined;
|
multiplyAndAddUnsafe(Q: PointType, a: bigint, b: bigint): PointType | undefined;
|
||||||
}
|
}
|
||||||
// Static methods
|
// Static methods
|
||||||
export type PointConstructor = {
|
export interface PointConstructor extends GroupConstructor<PointType> {
|
||||||
BASE: PointType;
|
|
||||||
ZERO: PointType;
|
|
||||||
new (x: bigint, y: bigint): PointType;
|
new (x: bigint, y: bigint): PointType;
|
||||||
fromHex(hex: Hex): PointType;
|
fromHex(hex: Hex): PointType;
|
||||||
fromPrivateKey(privateKey: PrivKey): PointType;
|
fromPrivateKey(privateKey: PrivKey): PointType;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type PubKey = Hex | PointType;
|
export type PubKey = Hex | PointType;
|
||||||
|
|
||||||
@@ -268,6 +241,13 @@ export type CurveFn = {
|
|||||||
utils: {
|
utils: {
|
||||||
mod: (a: bigint, b?: bigint) => bigint;
|
mod: (a: bigint, b?: bigint) => bigint;
|
||||||
invert: (number: bigint, modulo?: bigint) => bigint;
|
invert: (number: bigint, modulo?: bigint) => bigint;
|
||||||
|
_bigintToBytes: (num: bigint) => Uint8Array;
|
||||||
|
_bigintToString: (num: bigint) => string;
|
||||||
|
_normalizePrivateKey: (key: PrivKey) => bigint;
|
||||||
|
_normalizePublicKey: (publicKey: PubKey) => PointType;
|
||||||
|
_isWithinCurveOrder: (num: bigint) => boolean;
|
||||||
|
_isValidFieldElement: (num: bigint) => boolean;
|
||||||
|
_weierstrassEquation: (x: bigint) => bigint;
|
||||||
isValidPrivateKey(privateKey: PrivKey): boolean;
|
isValidPrivateKey(privateKey: PrivKey): boolean;
|
||||||
hashToPrivateKey: (hash: Hex) => Uint8Array;
|
hashToPrivateKey: (hash: Hex) => Uint8Array;
|
||||||
randomPrivateKey: () => Uint8Array;
|
randomPrivateKey: () => Uint8Array;
|
||||||
@@ -287,8 +267,8 @@ class HmacDrbg {
|
|||||||
if (typeof qByteLen !== 'number' || qByteLen < 2) throw new Error('qByteLen must be a number');
|
if (typeof qByteLen !== 'number' || qByteLen < 2) throw new Error('qByteLen must be a number');
|
||||||
if (typeof hmacFn !== 'function') throw new Error('hmacFn must be a function');
|
if (typeof hmacFn !== 'function') throw new Error('hmacFn must be a function');
|
||||||
// Step B, Step C: set hashLen to 8*ceil(hlen/8)
|
// Step B, Step C: set hashLen to 8*ceil(hlen/8)
|
||||||
this.v = new Uint8Array(this.hashLen).fill(1);
|
this.v = new Uint8Array(hashLen).fill(1);
|
||||||
this.k = new Uint8Array(this.hashLen).fill(0);
|
this.k = new Uint8Array(hashLen).fill(0);
|
||||||
this.counter = 0;
|
this.counter = 0;
|
||||||
}
|
}
|
||||||
private hmacSync(...values: Uint8Array[]) {
|
private hmacSync(...values: Uint8Array[]) {
|
||||||
@@ -327,8 +307,11 @@ class HmacDrbg {
|
|||||||
// Use only input from curveOpts!
|
// Use only input from curveOpts!
|
||||||
export function weierstrass(curveDef: CurveType): CurveFn {
|
export function weierstrass(curveDef: CurveType): CurveFn {
|
||||||
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
|
const CURVE = validateOpts(curveDef) as ReturnType<typeof validateOpts>;
|
||||||
|
const CURVE_ORDER = CURVE.n;
|
||||||
// Lengths
|
// Lengths
|
||||||
const fieldLen = CURVE.nByteLength!; // 32 (length of one field element)
|
// All curves has same field / group length as for now, but it can be different for other curves
|
||||||
|
const groupLen = CURVE.nByteLength;
|
||||||
|
const fieldLen = CURVE.nByteLength; // 32 (length of one field element)
|
||||||
if (fieldLen > 2048) throw new Error('Field lengths over 2048 are not supported');
|
if (fieldLen > 2048) throw new Error('Field lengths over 2048 are not supported');
|
||||||
|
|
||||||
const compressedLen = fieldLen + 1; // 33
|
const compressedLen = fieldLen + 1; // 33
|
||||||
@@ -378,12 +361,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
} else if (typeof key === 'number' && Number.isSafeInteger(key) && key > 0) {
|
} else if (typeof key === 'number' && Number.isSafeInteger(key) && key > 0) {
|
||||||
num = BigInt(key);
|
num = BigInt(key);
|
||||||
} else if (typeof key === 'string') {
|
} else if (typeof key === 'string') {
|
||||||
key = key.padStart(2 * fieldLen, '0'); // Eth-like hexes
|
key = key.padStart(2 * groupLen, '0'); // Eth-like hexes
|
||||||
if (key.length !== 2 * fieldLen) throw new Error(`Expected ${fieldLen} bytes of private key`);
|
if (key.length !== 2 * groupLen) throw new Error(`Expected ${groupLen} bytes of private key`);
|
||||||
num = hexToNumber(key);
|
num = hexToNumber(key);
|
||||||
} else if (key instanceof Uint8Array) {
|
} else if (key instanceof Uint8Array) {
|
||||||
if (key.length !== fieldLen) throw new Error(`Expected ${fieldLen} bytes of private key`);
|
if (key.length !== groupLen) throw new Error(`Expected ${groupLen} bytes of private key`);
|
||||||
num = bytesToNumber(key);
|
num = bytesToNumberBE(key);
|
||||||
} else {
|
} else {
|
||||||
throw new TypeError('Expected valid private key');
|
throw new TypeError('Expected valid private key');
|
||||||
}
|
}
|
||||||
@@ -405,12 +388,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isBiggerThanHalfOrder(number: bigint) {
|
function isBiggerThanHalfOrder(number: bigint) {
|
||||||
const HALF = CURVE.n >> _1n;
|
const HALF = CURVE_ORDER >> _1n;
|
||||||
return number > HALF;
|
return number > HALF;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeS(s: bigint) {
|
function normalizeS(s: bigint) {
|
||||||
return isBiggerThanHalfOrder(s) ? mod.mod(-s, CURVE.n) : s;
|
return isBiggerThanHalfOrder(s) ? mod.mod(-s, CURVE_ORDER) : s;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeScalar(num: number | bigint): bigint {
|
function normalizeScalar(num: number | bigint): bigint {
|
||||||
@@ -426,7 +409,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
const { n, nBitLength } = CURVE;
|
const { n, nBitLength } = CURVE;
|
||||||
const byteLength = hash.length;
|
const byteLength = hash.length;
|
||||||
const delta = byteLength * 8 - nBitLength; // size of curve.n (252 bits)
|
const delta = byteLength * 8 - nBitLength; // size of curve.n (252 bits)
|
||||||
let h = bytesToNumber(hash);
|
let h = bytesToNumberBE(hash);
|
||||||
if (delta > 0) h = h >> BigInt(delta);
|
if (delta > 0) h = h >> BigInt(delta);
|
||||||
if (!truncateOnly && h >= n) h -= n;
|
if (!truncateOnly && h >= n) h -= n;
|
||||||
return h;
|
return h;
|
||||||
@@ -499,6 +482,23 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
double(): JacobianPoint {
|
double(): JacobianPoint {
|
||||||
const { x: X1, y: Y1, z: Z1 } = this;
|
const { x: X1, y: Y1, z: Z1 } = this;
|
||||||
const { a } = CURVE;
|
const { a } = CURVE;
|
||||||
|
|
||||||
|
// Faster algorithm: when a=0
|
||||||
|
// From: https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
|
||||||
|
// Cost: 2M + 5S + 6add + 3*2 + 1*3 + 1*8.
|
||||||
|
if (a === _0n) {
|
||||||
|
const A = modP(X1 * X1);
|
||||||
|
const B = modP(Y1 * Y1);
|
||||||
|
const C = modP(B * B);
|
||||||
|
const x1b = X1 + B;
|
||||||
|
const D = modP(_2n * (modP(x1b * x1b) - A - C));
|
||||||
|
const E = modP(_3n * A);
|
||||||
|
const F = modP(E * E);
|
||||||
|
const X3 = modP(F - _2n * D);
|
||||||
|
const Y3 = modP(E * (D - X3) - _8n * C);
|
||||||
|
const Z3 = modP(_2n * Y1 * Z1);
|
||||||
|
return new JacobianPoint(X3, Y3, Z3);
|
||||||
|
}
|
||||||
const XX = modP(X1 * X1);
|
const XX = modP(X1 * X1);
|
||||||
const YY = modP(Y1 * Y1);
|
const YY = modP(Y1 * Y1);
|
||||||
const YYYY = modP(YY * YY);
|
const YYYY = modP(YY * YY);
|
||||||
@@ -520,8 +520,6 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
// Note: 2007 Bernstein-Lange (11M + 5S + 9add + 4*2) is actually 10% slower.
|
// Note: 2007 Bernstein-Lange (11M + 5S + 9add + 4*2) is actually 10% slower.
|
||||||
add(other: JacobianPoint): JacobianPoint {
|
add(other: JacobianPoint): JacobianPoint {
|
||||||
if (!(other instanceof JacobianPoint)) throw new TypeError('JacobianPoint expected');
|
if (!(other instanceof JacobianPoint)) throw new TypeError('JacobianPoint expected');
|
||||||
// TODO: remove
|
|
||||||
if (this.equals(JacobianPoint.ZERO)) return other;
|
|
||||||
const { x: X1, y: Y1, z: Z1 } = this;
|
const { x: X1, y: Y1, z: Z1 } = this;
|
||||||
const { x: X2, y: Y2, z: Z2 } = other;
|
const { x: X2, y: Y2, z: Z2 } = other;
|
||||||
if (X2 === _0n || Y2 === _0n) return this;
|
if (X2 === _0n || Y2 === _0n) return this;
|
||||||
@@ -562,16 +560,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
let n = normalizeScalar(scalar);
|
let n = normalizeScalar(scalar);
|
||||||
if (n === _1n) return this;
|
if (n === _1n) return this;
|
||||||
|
|
||||||
if (!CURVE.endo) {
|
if (!CURVE.endo) return wnaf.unsafeLadder(this, n);
|
||||||
let p = P0;
|
|
||||||
let d: JacobianPoint = this;
|
|
||||||
while (n > _0n) {
|
|
||||||
if (n & _1n) p = p.add(d);
|
|
||||||
d = d.double();
|
|
||||||
n >>= _1n;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply endomorphism
|
// Apply endomorphism
|
||||||
let { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n);
|
let { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n);
|
||||||
@@ -591,106 +580,22 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
return k1p.add(k2p);
|
return k1p.add(k2p);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a wNAF precomputation window. Used for caching.
|
|
||||||
* Default window size is set by `utils.precompute()` and is equal to 8.
|
|
||||||
* Which means we are caching 65536 points: 256 points for every bit from 0 to 256.
|
|
||||||
* @returns 65K precomputed points, depending on W
|
|
||||||
*/
|
|
||||||
private precomputeWindow(W: number): JacobianPoint[] {
|
|
||||||
const windows = CURVE.endo
|
|
||||||
? Math.ceil(CURVE.nBitLength / 2) / W + 1
|
|
||||||
: CURVE.nBitLength / W + 1;
|
|
||||||
const points: JacobianPoint[] = [];
|
|
||||||
let p: JacobianPoint = this;
|
|
||||||
let base = p;
|
|
||||||
for (let window = 0; window < windows; window++) {
|
|
||||||
base = p;
|
|
||||||
points.push(base);
|
|
||||||
for (let i = 1; i < 2 ** (W - 1); i++) {
|
|
||||||
base = base.add(p);
|
|
||||||
points.push(base);
|
|
||||||
}
|
|
||||||
p = base.double();
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements w-ary non-adjacent form for calculating ec multiplication.
|
* Implements w-ary non-adjacent form for calculating ec multiplication.
|
||||||
* @param n
|
|
||||||
* @param affinePoint optional 2d point to save cached precompute windows on it.
|
|
||||||
* @returns real and fake (for const-time) points
|
|
||||||
*/
|
*/
|
||||||
private wNAF(n: bigint, affinePoint?: Point): { p: JacobianPoint; f: JacobianPoint } {
|
private wNAF(n: bigint, affinePoint?: Point): { p: JacobianPoint; f: JacobianPoint } {
|
||||||
if (!affinePoint && this.equals(JacobianPoint.BASE)) affinePoint = Point.BASE;
|
if (!affinePoint && this.equals(JacobianPoint.BASE)) affinePoint = Point.BASE;
|
||||||
const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1;
|
const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1;
|
||||||
if (256 % W) {
|
|
||||||
throw new Error('Point#wNAF: Invalid precomputation window, must be power of 2');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate precomputes on a first run, reuse them after
|
// Calculate precomputes on a first run, reuse them after
|
||||||
let precomputes = affinePoint && pointPrecomputes.get(affinePoint);
|
let precomputes = affinePoint && pointPrecomputes.get(affinePoint);
|
||||||
if (!precomputes) {
|
if (!precomputes) {
|
||||||
precomputes = this.precomputeWindow(W);
|
precomputes = wnaf.precomputeWindow(this, W) as JacobianPoint[];
|
||||||
if (affinePoint && W !== 1) {
|
if (affinePoint && W !== 1) {
|
||||||
precomputes = JacobianPoint.normalizeZ(precomputes);
|
precomputes = JacobianPoint.normalizeZ(precomputes);
|
||||||
pointPrecomputes.set(affinePoint, precomputes);
|
pointPrecomputes.set(affinePoint, precomputes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return wnaf.wNAF(W, precomputes, n);
|
||||||
// Initialize real and fake points for const-time
|
|
||||||
let p = JacobianPoint.ZERO;
|
|
||||||
// Should be G (base) point, since otherwise f can be infinity point in the end
|
|
||||||
let f = JacobianPoint.BASE;
|
|
||||||
|
|
||||||
const nBits = CURVE.endo ? CURVE.nBitLength / 2 : CURVE.nBitLength;
|
|
||||||
const windows = 1 + Math.ceil(nBits / W); // W=8 17
|
|
||||||
const windowSize = 2 ** (W - 1); // W=8 128
|
|
||||||
const mask = BigInt(2 ** W - 1); // Create mask with W ones: 0b11111111 for W=8
|
|
||||||
const maxNumber = 2 ** W; // W=8 256
|
|
||||||
const shiftBy = BigInt(W); // W=8 8
|
|
||||||
|
|
||||||
for (let window = 0; window < windows; window++) {
|
|
||||||
const offset = window * windowSize;
|
|
||||||
// Extract W bits.
|
|
||||||
let wbits = Number(n & mask);
|
|
||||||
|
|
||||||
// Shift number by W bits.
|
|
||||||
n >>= shiftBy;
|
|
||||||
|
|
||||||
// If the bits are bigger than max size, we'll split those.
|
|
||||||
// +224 => 256 - 32
|
|
||||||
if (wbits > windowSize) {
|
|
||||||
wbits -= maxNumber;
|
|
||||||
n += _1n;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This code was first written with assumption that 'f' and 'p' will never be infinity point:
|
|
||||||
// since each addition is multiplied by 2 ** W, it cannot cancel each other. However,
|
|
||||||
// there is negate now: it is possible that negated element from low value
|
|
||||||
// would be the same as high element, which will create carry into next window.
|
|
||||||
// It's not obvious how this can fail, but still worth investigating later.
|
|
||||||
|
|
||||||
// Check if we're onto Zero point.
|
|
||||||
// Add random point inside current window to f.
|
|
||||||
const offset1 = offset;
|
|
||||||
const offset2 = offset + Math.abs(wbits) - 1;
|
|
||||||
const cond1 = window % 2 !== 0;
|
|
||||||
const cond2 = wbits < 0;
|
|
||||||
if (wbits === 0) {
|
|
||||||
// The most important part for const-time getPublicKey
|
|
||||||
f = f.add(constTimeNegate(cond1, precomputes[offset1]));
|
|
||||||
} else {
|
|
||||||
p = p.add(constTimeNegate(cond2, precomputes[offset2]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// JIT-compiler should not eliminate f here, since it will later be used in normalizeZ()
|
|
||||||
// Even if the variable is still unused, there are some checks which will
|
|
||||||
// throw an exception, so compiler needs to prove they won't happen, which is hard.
|
|
||||||
// At this point there is a way to F be infinity-point even if p is not,
|
|
||||||
// which makes it less const-time: around 1 bigint multiply.
|
|
||||||
return { p, f };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -712,8 +617,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
const { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n);
|
const { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n);
|
||||||
let { p: k1p, f: f1p } = this.wNAF(k1, affinePoint);
|
let { p: k1p, f: f1p } = this.wNAF(k1, affinePoint);
|
||||||
let { p: k2p, f: f2p } = this.wNAF(k2, affinePoint);
|
let { p: k2p, f: f2p } = this.wNAF(k2, affinePoint);
|
||||||
k1p = constTimeNegate(k1neg, k1p);
|
k1p = wnaf.constTimeNegate(k1neg, k1p);
|
||||||
k2p = constTimeNegate(k2neg, k2p);
|
k2p = wnaf.constTimeNegate(k2neg, k2p);
|
||||||
k2p = new JacobianPoint(modP(k2p.x * CURVE.endo.beta), k2p.y, k2p.z);
|
k2p = new JacobianPoint(modP(k2p.x * CURVE.endo.beta), k2p.y, k2p.z);
|
||||||
point = k1p.add(k2p);
|
point = k1p.add(k2p);
|
||||||
fake = f1p.add(f2p);
|
fake = f1p.add(f2p);
|
||||||
@@ -747,11 +652,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
return new Point(ax, ay);
|
return new Point(ax, ay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Const-time utility for wNAF
|
const wnaf = wNAF(JacobianPoint, CURVE.endo ? CURVE.nBitLength / 2 : CURVE.nBitLength);
|
||||||
function constTimeNegate(condition: boolean, item: JacobianPoint) {
|
|
||||||
const neg = item.negate();
|
|
||||||
return condition ? neg : item;
|
|
||||||
}
|
|
||||||
// Stores precomputed values for points.
|
// Stores precomputed values for points.
|
||||||
const pointPrecomputes = new WeakMap<Point, JacobianPoint[]>();
|
const pointPrecomputes = new WeakMap<Point, JacobianPoint[]>();
|
||||||
|
|
||||||
@@ -787,13 +688,12 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supports compressed ECDSA (33-byte) points
|
* Supports compressed ECDSA points
|
||||||
* @param bytes 33 bytes
|
|
||||||
* @returns Point instance
|
* @returns Point instance
|
||||||
*/
|
*/
|
||||||
private static fromCompressedHex(bytes: Uint8Array) {
|
private static fromCompressedHex(bytes: Uint8Array) {
|
||||||
const P = CURVE.P;
|
const P = CURVE.P;
|
||||||
const x = bytesToNumber(bytes.subarray(1));
|
const x = bytesToNumberBE(bytes.subarray(1));
|
||||||
if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
|
if (!isValidFieldElement(x)) throw new Error('Point is not on curve');
|
||||||
const y2 = weierstrassEquation(x); // y² = x³ + ax + b
|
const y2 = weierstrassEquation(x); // y² = x³ + ax + b
|
||||||
let y = sqrtModCurve(y2, P); // y = y² ^ (p+1)/4
|
let y = sqrtModCurve(y2, P); // y = y² ^ (p+1)/4
|
||||||
@@ -807,8 +707,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static fromUncompressedHex(bytes: Uint8Array) {
|
private static fromUncompressedHex(bytes: Uint8Array) {
|
||||||
const x = bytesToNumber(bytes.subarray(1, fieldLen + 1));
|
const x = bytesToNumberBE(bytes.subarray(1, fieldLen + 1));
|
||||||
const y = bytesToNumber(bytes.subarray(fieldLen + 1, 2 * fieldLen + 1));
|
const y = bytesToNumberBE(bytes.subarray(fieldLen + 1, 2 * fieldLen + 1));
|
||||||
const point = new Point(x, y);
|
const point = new Point(x, y);
|
||||||
point.assertValidity();
|
point.assertValidity();
|
||||||
return point;
|
return point;
|
||||||
@@ -816,7 +716,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts hash string or Uint8Array to Point.
|
* Converts hash string or Uint8Array to Point.
|
||||||
* @param hex 33/65-byte (ECDSA) hex
|
* @param hex short/long ECDSA hex
|
||||||
*/
|
*/
|
||||||
static fromHex(hex: Hex): Point {
|
static fromHex(hex: Hex): Point {
|
||||||
const bytes = ensureBytes(hex);
|
const bytes = ensureBytes(hex);
|
||||||
@@ -861,6 +761,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
equals(other: Point): boolean {
|
equals(other: Point): boolean {
|
||||||
|
if (!(other instanceof Point)) throw new TypeError('Point#equals: expected Point');
|
||||||
return this.x === other.x && this.y === other.y;
|
return this.x === other.x && this.y === other.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -986,7 +887,7 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
|
|
||||||
normalizeS() {
|
normalizeS() {
|
||||||
return this.hasHighS()
|
return this.hasHighS()
|
||||||
? new Signature(this.r, mod.mod(-this.s, CURVE.n), this.recovery)
|
? new Signature(this.r, mod.mod(-this.s, CURVE_ORDER), this.recovery)
|
||||||
: this;
|
: this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1025,28 +926,22 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
_bigintToBytes: numToField,
|
_bigintToBytes: numToField,
|
||||||
|
_bigintToString: numToFieldStr,
|
||||||
_normalizePrivateKey: normalizePrivateKey,
|
_normalizePrivateKey: normalizePrivateKey,
|
||||||
|
_normalizePublicKey: normalizePublicKey,
|
||||||
|
_isWithinCurveOrder: isWithinCurveOrder,
|
||||||
|
_isValidFieldElement: isValidFieldElement,
|
||||||
|
_weierstrassEquation: weierstrassEquation,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can take (keyLength + 8) or more bytes of uniform input e.g. from CSPRNG or KDF
|
* Converts some bytes to a valid private key. Needs at least (nBitLength+64) bytes.
|
||||||
* and convert them into private key, with the modulo bias being neglible.
|
|
||||||
* As per FIPS 186 B.4.1.
|
|
||||||
* https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/
|
|
||||||
* @param hash hash output from sha512, or a similar function
|
|
||||||
* @returns valid private key
|
|
||||||
*/
|
*/
|
||||||
hashToPrivateKey: (hash: Hex): Uint8Array => {
|
hashToPrivateKey: (hash: Hex): Uint8Array => numToField(hashToPrivateScalar(hash, CURVE_ORDER)),
|
||||||
hash = ensureBytes(hash);
|
|
||||||
const minLen = fieldLen + 8;
|
|
||||||
if (hash.length < minLen || hash.length > 1024) {
|
|
||||||
throw new Error(`Expected ${minLen}-1024 bytes of private key as per FIPS 186`);
|
|
||||||
}
|
|
||||||
const num = mod.mod(bytesToNumber(hash), CURVE.n - _1n) + _1n;
|
|
||||||
return numToField(num);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Takes curve order + 64 bits from CSPRNG
|
/**
|
||||||
// so that modulo bias is neglible, matches FIPS 186 B.4.1.
|
* Produces cryptographically secure private key from random of size (nBitLength+64)
|
||||||
|
* as per FIPS 186 B.4.1 with modulo bias being neglible.
|
||||||
|
*/
|
||||||
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(CURVE.randomBytes(fieldLen + 8)),
|
randomPrivateKey: (): Uint8Array => utils.hashToPrivateKey(CURVE.randomBytes(fieldLen + 8)),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1067,8 +962,8 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes public key for a private key.
|
* Computes public key for a private key.
|
||||||
* @param privateKey 32-byte private key
|
* @param privateKey private key
|
||||||
* @param isCompressed whether to return compact (33-byte), or full (65-byte) key
|
* @param isCompressed whether to return compact, or full key
|
||||||
* @returns Public key, full by default; short when isCompressed=true
|
* @returns Public key, full by default; short when isCompressed=true
|
||||||
*/
|
*/
|
||||||
function getPublicKey(privateKey: PrivKey, isCompressed = false): Uint8Array {
|
function getPublicKey(privateKey: PrivKey, isCompressed = false): Uint8Array {
|
||||||
@@ -1108,11 +1003,11 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
// RFC6979 methods
|
// RFC6979 methods
|
||||||
function bits2int(bytes: Uint8Array) {
|
function bits2int(bytes: Uint8Array) {
|
||||||
const slice = bytes.length > fieldLen ? bytes.slice(0, fieldLen) : bytes;
|
const slice = bytes.length > fieldLen ? bytes.slice(0, fieldLen) : bytes;
|
||||||
return bytesToNumber(slice);
|
return bytesToNumberBE(slice);
|
||||||
}
|
}
|
||||||
function bits2octets(bytes: Uint8Array): Uint8Array {
|
function bits2octets(bytes: Uint8Array): Uint8Array {
|
||||||
const z1 = bits2int(bytes);
|
const z1 = bits2int(bytes);
|
||||||
const z2 = mod.mod(z1, CURVE.n);
|
const z2 = mod.mod(z1, CURVE_ORDER);
|
||||||
return int2octets(z2 < _0n ? z1 : z2);
|
return int2octets(z2 < _0n ? z1 : z2);
|
||||||
}
|
}
|
||||||
function int2octets(num: bigint): Uint8Array {
|
function int2octets(num: bigint): Uint8Array {
|
||||||
@@ -1253,7 +1148,6 @@ export function weierstrass(curveDef: CurveType): CurveFn {
|
|||||||
getSharedSecret,
|
getSharedSecret,
|
||||||
sign,
|
sign,
|
||||||
verify,
|
verify,
|
||||||
|
|
||||||
Point,
|
Point,
|
||||||
JacobianPoint,
|
JacobianPoint,
|
||||||
Signature,
|
Signature,
|
||||||
Reference in New Issue
Block a user