ox 0.14.34 → 0.14.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/_cjs/core/AesGcm.js +47 -1
- package/_cjs/core/AesGcm.js.map +1 -1
- package/_cjs/core/Ed25519.js +42 -1
- package/_cjs/core/Ed25519.js.map +1 -1
- package/_cjs/core/P256.js +44 -1
- package/_cjs/core/P256.js.map +1 -1
- package/_cjs/core/Secp256k1.js +38 -1
- package/_cjs/core/Secp256k1.js.map +1 -1
- package/_cjs/core/internal/keyDerivation.js +15 -0
- package/_cjs/core/internal/keyDerivation.js.map +1 -0
- package/_cjs/tempo/MultisigConfig.js +15 -4
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/core/AesGcm.js +85 -0
- package/_esm/core/AesGcm.js.map +1 -1
- package/_esm/core/Ed25519.js +81 -0
- package/_esm/core/Ed25519.js.map +1 -1
- package/_esm/core/P256.js +83 -0
- package/_esm/core/P256.js.map +1 -1
- package/_esm/core/Secp256k1.js +77 -0
- package/_esm/core/Secp256k1.js.map +1 -1
- package/_esm/core/internal/keyDerivation.js +13 -0
- package/_esm/core/internal/keyDerivation.js.map +1 -0
- package/_esm/tempo/MultisigConfig.js +17 -5
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/version.js +1 -1
- package/_types/core/AesGcm.d.ts +66 -1
- package/_types/core/AesGcm.d.ts.map +1 -1
- package/_types/core/Ed25519.d.ts +82 -1
- package/_types/core/Ed25519.d.ts.map +1 -1
- package/_types/core/P256.d.ts +82 -1
- package/_types/core/P256.d.ts.map +1 -1
- package/_types/core/Secp256k1.d.ts +85 -1
- package/_types/core/Secp256k1.d.ts.map +1 -1
- package/_types/core/internal/keyDerivation.d.ts +12 -0
- package/_types/core/internal/keyDerivation.d.ts.map +1 -0
- package/_types/tempo/MultisigConfig.d.ts +4 -3
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/core/AesGcm.ts +137 -1
- package/core/Ed25519.ts +133 -1
- package/core/P256.ts +135 -1
- package/core/Secp256k1.ts +133 -1
- package/core/internal/keyDerivation.ts +33 -0
- package/package.json +1 -1
- package/tempo/MultisigConfig.test.ts +40 -6
- package/tempo/MultisigConfig.ts +18 -5
- package/version.ts +1 -1
package/core/Ed25519.ts
CHANGED
|
@@ -4,8 +4,10 @@ import {
|
|
|
4
4
|
edwardsToMontgomeryPub,
|
|
5
5
|
} from '@noble/curves/ed25519'
|
|
6
6
|
import * as Bytes from './Bytes.js'
|
|
7
|
-
import
|
|
7
|
+
import * as Errors from './Errors.js'
|
|
8
8
|
import * as Hex from './Hex.js'
|
|
9
|
+
import * as keyDerivation from './internal/keyDerivation.js'
|
|
10
|
+
import * as Mnemonic from './Mnemonic.js'
|
|
9
11
|
|
|
10
12
|
/** Re-export of noble/curves Ed25519 utilities. */
|
|
11
13
|
export const noble = ed25519
|
|
@@ -61,6 +63,115 @@ export declare namespace createKeyPair {
|
|
|
61
63
|
| Errors.GlobalErrorType
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Derives an Ed25519 private key from a BIP-39 mnemonic.
|
|
68
|
+
*
|
|
69
|
+
* This is equivalent to passing `Mnemonic.toSeed(mnemonic, { passphrase })`
|
|
70
|
+
* to `Ed25519.fromSeed`.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts twoslash
|
|
74
|
+
* import { Ed25519 } from 'ox'
|
|
75
|
+
*
|
|
76
|
+
* const privateKey = Ed25519.fromMnemonic(
|
|
77
|
+
* 'test test test test test test test test test test test junk'
|
|
78
|
+
* )
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @param mnemonic - BIP-39 mnemonic phrase.
|
|
82
|
+
* @param options - Options.
|
|
83
|
+
* @returns An Ed25519 private key.
|
|
84
|
+
*/
|
|
85
|
+
export function fromMnemonic<as extends 'Hex' | 'Bytes' = 'Hex'>(
|
|
86
|
+
mnemonic: string,
|
|
87
|
+
options: fromMnemonic.Options<as> = {},
|
|
88
|
+
): fromMnemonic.ReturnType<as> {
|
|
89
|
+
const { passphrase } = options
|
|
90
|
+
const seed = Mnemonic.toSeed(mnemonic, { passphrase })
|
|
91
|
+
try {
|
|
92
|
+
return fromSeed(seed, options)
|
|
93
|
+
} finally {
|
|
94
|
+
seed.fill(0)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export declare namespace fromMnemonic {
|
|
99
|
+
type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
|
|
100
|
+
/**
|
|
101
|
+
* Format of the returned private key.
|
|
102
|
+
* @default 'Hex'
|
|
103
|
+
*/
|
|
104
|
+
as?: as | 'Hex' | 'Bytes' | undefined
|
|
105
|
+
/** Optional BIP-39 passphrase. */
|
|
106
|
+
passphrase?: string | undefined
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type ReturnType<as extends 'Hex' | 'Bytes'> = fromSeed.ReturnType<as>
|
|
110
|
+
|
|
111
|
+
type ErrorType = fromSeed.ErrorType
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Derives an Ed25519 private key from a seed.
|
|
116
|
+
*
|
|
117
|
+
* The seed must contain at least 32 bytes of cryptographically strong key
|
|
118
|
+
* material. Do not pass a password directly; use a password KDF first.
|
|
119
|
+
*
|
|
120
|
+
* The permanent derivation contract uses the seed as the HMAC-SHA256
|
|
121
|
+
* key. The HMAC message uses the `ox.ed25519.fromSeed.v1` domain followed by a
|
|
122
|
+
* 32-bit big-endian counter set to zero.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts twoslash
|
|
126
|
+
* import { Ed25519 } from 'ox'
|
|
127
|
+
*
|
|
128
|
+
* const privateKey = Ed25519.fromSeed(
|
|
129
|
+
* '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
|
|
130
|
+
* )
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* @param seed - Seed containing at least 32 bytes of cryptographically strong key material.
|
|
134
|
+
* @param options - Options.
|
|
135
|
+
* @returns An Ed25519 private key.
|
|
136
|
+
*/
|
|
137
|
+
export function fromSeed<as extends 'Hex' | 'Bytes' = 'Hex'>(
|
|
138
|
+
seed: Hex.Hex | Bytes.Bytes,
|
|
139
|
+
options: fromSeed.Options<as> = {},
|
|
140
|
+
): fromSeed.ReturnType<as> {
|
|
141
|
+
const { as = 'Hex' } = options
|
|
142
|
+
const bytes = Bytes.from(seed)
|
|
143
|
+
if (bytes.length < 32) throw new InvalidSeedSizeError({ size: bytes.length })
|
|
144
|
+
|
|
145
|
+
const privateKey = keyDerivation.derive(bytes, fromSeedDomain)
|
|
146
|
+
if (as === 'Hex') {
|
|
147
|
+
const value = Hex.fromBytes(privateKey)
|
|
148
|
+
privateKey.fill(0)
|
|
149
|
+
return value as never
|
|
150
|
+
}
|
|
151
|
+
return privateKey as never
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export declare namespace fromSeed {
|
|
155
|
+
type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
|
|
156
|
+
/**
|
|
157
|
+
* Format of the returned private key.
|
|
158
|
+
* @default 'Hex'
|
|
159
|
+
*/
|
|
160
|
+
as?: as | 'Hex' | 'Bytes' | undefined
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type ReturnType<as extends 'Hex' | 'Bytes'> =
|
|
164
|
+
| (as extends 'Bytes' ? Bytes.Bytes : never)
|
|
165
|
+
| (as extends 'Hex' ? Hex.Hex : never)
|
|
166
|
+
|
|
167
|
+
type ErrorType =
|
|
168
|
+
| Bytes.from.ErrorType
|
|
169
|
+
| Hex.fromBytes.ErrorType
|
|
170
|
+
| keyDerivation.derive.ErrorType
|
|
171
|
+
| InvalidSeedSizeError
|
|
172
|
+
| Errors.GlobalErrorType
|
|
173
|
+
}
|
|
174
|
+
|
|
64
175
|
/**
|
|
65
176
|
* Computes the Ed25519 public key from a provided private key.
|
|
66
177
|
*
|
|
@@ -337,3 +448,24 @@ export declare namespace toX25519PrivateKey {
|
|
|
337
448
|
| Hex.fromBytes.ErrorType
|
|
338
449
|
| Errors.GlobalErrorType
|
|
339
450
|
}
|
|
451
|
+
|
|
452
|
+
/** Thrown when a seed contains fewer than 32 bytes. */
|
|
453
|
+
export class InvalidSeedSizeError extends Errors.BaseError {
|
|
454
|
+
override readonly name = 'Ed25519.InvalidSeedSizeError'
|
|
455
|
+
|
|
456
|
+
constructor(options: InvalidSeedSizeError.Options) {
|
|
457
|
+
super(
|
|
458
|
+
`Seed must contain at least 32 bytes. Received ${options.size} bytes.`,
|
|
459
|
+
)
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export declare namespace InvalidSeedSizeError {
|
|
464
|
+
/** Options for `Ed25519.InvalidSeedSizeError`. */
|
|
465
|
+
type Options = {
|
|
466
|
+
/** Received seed size. */
|
|
467
|
+
size: number
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const fromSeedDomain = Bytes.fromString('ox.ed25519.fromSeed.v1')
|
package/core/P256.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { secp256r1 } from '@noble/curves/p256'
|
|
2
2
|
import * as Bytes from './Bytes.js'
|
|
3
|
-
import
|
|
3
|
+
import * as Errors from './Errors.js'
|
|
4
4
|
import * as Hex from './Hex.js'
|
|
5
5
|
import * as Entropy from './internal/entropy.js'
|
|
6
|
+
import * as keyDerivation from './internal/keyDerivation.js'
|
|
7
|
+
import * as Mnemonic from './Mnemonic.js'
|
|
6
8
|
import * as PublicKey from './PublicKey.js'
|
|
7
9
|
import type * as Signature from './Signature.js'
|
|
8
10
|
|
|
@@ -57,6 +59,117 @@ export declare namespace createKeyPair {
|
|
|
57
59
|
| Errors.GlobalErrorType
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Derives a valid P256 private key from a BIP-39 mnemonic.
|
|
64
|
+
*
|
|
65
|
+
* This is equivalent to passing `Mnemonic.toSeed(mnemonic, { passphrase })`
|
|
66
|
+
* to `P256.fromSeed`.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts twoslash
|
|
70
|
+
* import { P256 } from 'ox'
|
|
71
|
+
*
|
|
72
|
+
* const privateKey = P256.fromMnemonic(
|
|
73
|
+
* 'test test test test test test test test test test test junk'
|
|
74
|
+
* )
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @param mnemonic - BIP-39 mnemonic phrase.
|
|
78
|
+
* @param options - Options.
|
|
79
|
+
* @returns A valid P256 private key.
|
|
80
|
+
*/
|
|
81
|
+
export function fromMnemonic<as extends 'Hex' | 'Bytes' = 'Hex'>(
|
|
82
|
+
mnemonic: string,
|
|
83
|
+
options: fromMnemonic.Options<as> = {},
|
|
84
|
+
): fromMnemonic.ReturnType<as> {
|
|
85
|
+
const { passphrase } = options
|
|
86
|
+
const seed = Mnemonic.toSeed(mnemonic, { passphrase })
|
|
87
|
+
try {
|
|
88
|
+
return fromSeed(seed, options)
|
|
89
|
+
} finally {
|
|
90
|
+
seed.fill(0)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export declare namespace fromMnemonic {
|
|
95
|
+
type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
|
|
96
|
+
/**
|
|
97
|
+
* Format of the returned private key.
|
|
98
|
+
* @default 'Hex'
|
|
99
|
+
*/
|
|
100
|
+
as?: as | 'Hex' | 'Bytes' | undefined
|
|
101
|
+
/** Optional BIP-39 passphrase. */
|
|
102
|
+
passphrase?: string | undefined
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
type ReturnType<as extends 'Hex' | 'Bytes'> = fromSeed.ReturnType<as>
|
|
106
|
+
|
|
107
|
+
type ErrorType = fromSeed.ErrorType
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Derives a valid P256 private key from a seed.
|
|
112
|
+
*
|
|
113
|
+
* The seed must contain at least 32 bytes of cryptographically strong key
|
|
114
|
+
* material. Do not pass a password directly; use a password KDF first.
|
|
115
|
+
*
|
|
116
|
+
* The permanent derivation contract uses the seed as the HMAC-SHA256
|
|
117
|
+
* key. The HMAC message uses the `ox.p256.fromSeed.v1` domain followed by a
|
|
118
|
+
* 32-bit big-endian counter starting at zero. Invalid scalars are skipped.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts twoslash
|
|
122
|
+
* import { P256 } from 'ox'
|
|
123
|
+
*
|
|
124
|
+
* const privateKey = P256.fromSeed(
|
|
125
|
+
* '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
|
|
126
|
+
* )
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* @param seed - Seed containing at least 32 bytes of cryptographically strong key material.
|
|
130
|
+
* @param options - Options.
|
|
131
|
+
* @returns A valid P256 private key.
|
|
132
|
+
*/
|
|
133
|
+
export function fromSeed<as extends 'Hex' | 'Bytes' = 'Hex'>(
|
|
134
|
+
seed: Hex.Hex | Bytes.Bytes,
|
|
135
|
+
options: fromSeed.Options<as> = {},
|
|
136
|
+
): fromSeed.ReturnType<as> {
|
|
137
|
+
const { as = 'Hex' } = options
|
|
138
|
+
const bytes = Bytes.from(seed)
|
|
139
|
+
if (bytes.length < 32) throw new InvalidSeedSizeError({ size: bytes.length })
|
|
140
|
+
|
|
141
|
+
const privateKey = keyDerivation.derive(bytes, fromSeedDomain, {
|
|
142
|
+
validate: noble.utils.isValidPrivateKey,
|
|
143
|
+
})
|
|
144
|
+
if (as === 'Hex') {
|
|
145
|
+
const value = Hex.fromBytes(privateKey)
|
|
146
|
+
privateKey.fill(0)
|
|
147
|
+
return value as never
|
|
148
|
+
}
|
|
149
|
+
return privateKey as never
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export declare namespace fromSeed {
|
|
153
|
+
type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
|
|
154
|
+
/**
|
|
155
|
+
* Format of the returned private key.
|
|
156
|
+
* @default 'Hex'
|
|
157
|
+
*/
|
|
158
|
+
as?: as | 'Hex' | 'Bytes' | undefined
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
type ReturnType<as extends 'Hex' | 'Bytes'> =
|
|
162
|
+
| (as extends 'Bytes' ? Bytes.Bytes : never)
|
|
163
|
+
| (as extends 'Hex' ? Hex.Hex : never)
|
|
164
|
+
|
|
165
|
+
type ErrorType =
|
|
166
|
+
| Bytes.from.ErrorType
|
|
167
|
+
| Hex.fromBytes.ErrorType
|
|
168
|
+
| keyDerivation.derive.ErrorType
|
|
169
|
+
| InvalidSeedSizeError
|
|
170
|
+
| Errors.GlobalErrorType
|
|
171
|
+
}
|
|
172
|
+
|
|
60
173
|
/**
|
|
61
174
|
* Computes the P256 ECDSA public key from a provided private key.
|
|
62
175
|
*
|
|
@@ -354,3 +467,24 @@ export declare namespace verify {
|
|
|
354
467
|
|
|
355
468
|
type ErrorType = Errors.GlobalErrorType
|
|
356
469
|
}
|
|
470
|
+
|
|
471
|
+
/** Thrown when a seed contains fewer than 32 bytes. */
|
|
472
|
+
export class InvalidSeedSizeError extends Errors.BaseError {
|
|
473
|
+
override readonly name = 'P256.InvalidSeedSizeError'
|
|
474
|
+
|
|
475
|
+
constructor(options: InvalidSeedSizeError.Options) {
|
|
476
|
+
super(
|
|
477
|
+
`Seed must contain at least 32 bytes. Received ${options.size} bytes.`,
|
|
478
|
+
)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export declare namespace InvalidSeedSizeError {
|
|
483
|
+
/** Options for `P256.InvalidSeedSizeError`. */
|
|
484
|
+
type Options = {
|
|
485
|
+
/** Received seed size. */
|
|
486
|
+
size: number
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const fromSeedDomain = Bytes.fromString('ox.p256.fromSeed.v1')
|
package/core/Secp256k1.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { secp256k1 } from '@noble/curves/secp256k1'
|
|
2
2
|
import * as Address from './Address.js'
|
|
3
3
|
import * as Bytes from './Bytes.js'
|
|
4
|
-
import
|
|
4
|
+
import * as Errors from './Errors.js'
|
|
5
5
|
import * as Hex from './Hex.js'
|
|
6
6
|
import * as Entropy from './internal/entropy.js'
|
|
7
|
+
import * as keyDerivation from './internal/keyDerivation.js'
|
|
7
8
|
import type { OneOf } from './internal/types.js'
|
|
9
|
+
import * as Mnemonic from './Mnemonic.js'
|
|
8
10
|
import * as PublicKey from './PublicKey.js'
|
|
9
11
|
import type * as Signature from './Signature.js'
|
|
10
12
|
|
|
@@ -59,6 +61,115 @@ export declare namespace createKeyPair {
|
|
|
59
61
|
| Errors.GlobalErrorType
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Derives a valid secp256k1 private key from a BIP-39 mnemonic.
|
|
66
|
+
*
|
|
67
|
+
* This is equivalent to `Mnemonic.toPrivateKey`, and derives the
|
|
68
|
+
* private key at `m/44'/60'/0'/0/0` by default.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts twoslash
|
|
72
|
+
* import { Secp256k1 } from 'ox'
|
|
73
|
+
*
|
|
74
|
+
* const privateKey = Secp256k1.fromMnemonic(
|
|
75
|
+
* 'test test test test test test test test test test test junk'
|
|
76
|
+
* )
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @param mnemonic - BIP-39 mnemonic phrase.
|
|
80
|
+
* @param options - Options.
|
|
81
|
+
* @returns A valid secp256k1 private key.
|
|
82
|
+
*/
|
|
83
|
+
export function fromMnemonic<as extends 'Hex' | 'Bytes' = 'Hex'>(
|
|
84
|
+
mnemonic: string,
|
|
85
|
+
options: fromMnemonic.Options<as> = {},
|
|
86
|
+
): fromMnemonic.ReturnType<as> {
|
|
87
|
+
const { as = 'Hex', passphrase, path } = options
|
|
88
|
+
return Mnemonic.toPrivateKey(mnemonic, { as, passphrase, path }) as never
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export declare namespace fromMnemonic {
|
|
92
|
+
type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
|
|
93
|
+
/**
|
|
94
|
+
* Format of the returned private key.
|
|
95
|
+
* @default 'Hex'
|
|
96
|
+
*/
|
|
97
|
+
as?: as | 'Hex' | 'Bytes' | undefined
|
|
98
|
+
/** Derivation path. @default `m/44'/60'/0'/0/0` */
|
|
99
|
+
path?: string | undefined
|
|
100
|
+
/** Optional BIP-39 passphrase. */
|
|
101
|
+
passphrase?: string | undefined
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type ReturnType<as extends 'Hex' | 'Bytes'> =
|
|
105
|
+
Mnemonic.toPrivateKey.ReturnType<as>
|
|
106
|
+
|
|
107
|
+
type ErrorType = Mnemonic.toPrivateKey.ErrorType
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Derives a valid secp256k1 private key from a seed.
|
|
112
|
+
*
|
|
113
|
+
* The seed must contain at least 32 bytes of cryptographically strong key
|
|
114
|
+
* material. Do not pass a password directly; use a password KDF first.
|
|
115
|
+
*
|
|
116
|
+
* The permanent derivation contract uses the seed as the HMAC-SHA256
|
|
117
|
+
* key. The HMAC message uses the `ox.secp256k1.fromSeed.v1` domain followed by
|
|
118
|
+
* a 32-bit big-endian counter starting at zero. Invalid scalars are skipped.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts twoslash
|
|
122
|
+
* import { Secp256k1 } from 'ox'
|
|
123
|
+
*
|
|
124
|
+
* const privateKey = Secp256k1.fromSeed(
|
|
125
|
+
* '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
|
|
126
|
+
* )
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* @param seed - Seed containing at least 32 bytes of cryptographically strong key material.
|
|
130
|
+
* @param options - Options.
|
|
131
|
+
* @returns A valid secp256k1 private key.
|
|
132
|
+
*/
|
|
133
|
+
export function fromSeed<as extends 'Hex' | 'Bytes' = 'Hex'>(
|
|
134
|
+
seed: Hex.Hex | Bytes.Bytes,
|
|
135
|
+
options: fromSeed.Options<as> = {},
|
|
136
|
+
): fromSeed.ReturnType<as> {
|
|
137
|
+
const { as = 'Hex' } = options
|
|
138
|
+
const bytes = Bytes.from(seed)
|
|
139
|
+
if (bytes.length < 32) throw new InvalidSeedSizeError({ size: bytes.length })
|
|
140
|
+
|
|
141
|
+
const privateKey = keyDerivation.derive(bytes, fromSeedDomain, {
|
|
142
|
+
validate: noble.utils.isValidPrivateKey,
|
|
143
|
+
})
|
|
144
|
+
if (as === 'Hex') {
|
|
145
|
+
const value = Hex.fromBytes(privateKey)
|
|
146
|
+
privateKey.fill(0)
|
|
147
|
+
return value as never
|
|
148
|
+
}
|
|
149
|
+
return privateKey as never
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export declare namespace fromSeed {
|
|
153
|
+
type Options<as extends 'Hex' | 'Bytes' = 'Hex'> = {
|
|
154
|
+
/**
|
|
155
|
+
* Format of the returned private key.
|
|
156
|
+
* @default 'Hex'
|
|
157
|
+
*/
|
|
158
|
+
as?: as | 'Hex' | 'Bytes' | undefined
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
type ReturnType<as extends 'Hex' | 'Bytes'> =
|
|
162
|
+
| (as extends 'Bytes' ? Bytes.Bytes : never)
|
|
163
|
+
| (as extends 'Hex' ? Hex.Hex : never)
|
|
164
|
+
|
|
165
|
+
type ErrorType =
|
|
166
|
+
| Bytes.from.ErrorType
|
|
167
|
+
| Hex.fromBytes.ErrorType
|
|
168
|
+
| keyDerivation.derive.ErrorType
|
|
169
|
+
| InvalidSeedSizeError
|
|
170
|
+
| Errors.GlobalErrorType
|
|
171
|
+
}
|
|
172
|
+
|
|
62
173
|
/**
|
|
63
174
|
* Computes the secp256k1 ECDSA public key from a provided private key.
|
|
64
175
|
*
|
|
@@ -423,3 +534,24 @@ export declare namespace verify {
|
|
|
423
534
|
|
|
424
535
|
type ErrorType = Errors.GlobalErrorType
|
|
425
536
|
}
|
|
537
|
+
|
|
538
|
+
/** Thrown when a seed contains fewer than 32 bytes. */
|
|
539
|
+
export class InvalidSeedSizeError extends Errors.BaseError {
|
|
540
|
+
override readonly name = 'Secp256k1.InvalidSeedSizeError'
|
|
541
|
+
|
|
542
|
+
constructor(options: InvalidSeedSizeError.Options) {
|
|
543
|
+
super(
|
|
544
|
+
`Seed must contain at least 32 bytes. Received ${options.size} bytes.`,
|
|
545
|
+
)
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export declare namespace InvalidSeedSizeError {
|
|
550
|
+
/** Options for `Secp256k1.InvalidSeedSizeError`. */
|
|
551
|
+
type Options = {
|
|
552
|
+
/** Received seed size. */
|
|
553
|
+
size: number
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const fromSeedDomain = Bytes.fromString('ox.secp256k1.fromSeed.v1')
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as Bytes from '../Bytes.js'
|
|
2
|
+
import type * as Errors from '../Errors.js'
|
|
3
|
+
import * as Hash from '../Hash.js'
|
|
4
|
+
|
|
5
|
+
/** @internal */
|
|
6
|
+
export function derive(
|
|
7
|
+
seed: Bytes.Bytes,
|
|
8
|
+
domain: Bytes.Bytes,
|
|
9
|
+
options: derive.Options = {},
|
|
10
|
+
): Bytes.Bytes {
|
|
11
|
+
const { validate } = options
|
|
12
|
+
for (let counter = 0; ; counter++) {
|
|
13
|
+
const key = Hash.hmac256(
|
|
14
|
+
seed,
|
|
15
|
+
Bytes.concat(domain, Bytes.fromNumber(counter, { size: 4 })),
|
|
16
|
+
{ as: 'Bytes' },
|
|
17
|
+
)
|
|
18
|
+
if (!validate || validate(key)) return key
|
|
19
|
+
key.fill(0)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export declare namespace derive {
|
|
24
|
+
type Options = {
|
|
25
|
+
validate?: ((key: Bytes.Bytes) => boolean) | undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type ErrorType =
|
|
29
|
+
| Bytes.concat.ErrorType
|
|
30
|
+
| Bytes.fromNumber.ErrorType
|
|
31
|
+
| Hash.hmac256.ErrorType
|
|
32
|
+
| Errors.GlobalErrorType
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -169,21 +169,21 @@ describe('assert / validate', () => {
|
|
|
169
169
|
expect(MultisigConfig.validate({ threshold: 1, owners: [] })).toBe(false)
|
|
170
170
|
})
|
|
171
171
|
|
|
172
|
-
test('accepts
|
|
173
|
-
const owners = Array.from({ length:
|
|
172
|
+
test('accepts 48 owners', () => {
|
|
173
|
+
const owners = Array.from({ length: 48 }, (_, i) => ({
|
|
174
174
|
owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
|
|
175
175
|
weight: 1,
|
|
176
176
|
}))
|
|
177
177
|
expect(
|
|
178
178
|
MultisigConfig.validate({
|
|
179
|
-
threshold: MultisigConfig.
|
|
179
|
+
threshold: MultisigConfig.maxSignatures,
|
|
180
180
|
owners,
|
|
181
181
|
}),
|
|
182
182
|
).toBe(true)
|
|
183
183
|
})
|
|
184
184
|
|
|
185
185
|
test('too many owners', () => {
|
|
186
|
-
const owners = Array.from({ length:
|
|
186
|
+
const owners = Array.from({ length: 49 }, (_, i) => ({
|
|
187
187
|
owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
|
|
188
188
|
weight: 1,
|
|
189
189
|
}))
|
|
@@ -199,11 +199,29 @@ describe('assert / validate', () => {
|
|
|
199
199
|
).toBe(false)
|
|
200
200
|
})
|
|
201
201
|
|
|
202
|
-
test('threshold
|
|
202
|
+
test('accepts a uint8 threshold above the signature limit', () => {
|
|
203
|
+
expect(
|
|
204
|
+
MultisigConfig.validate({
|
|
205
|
+
threshold: 9,
|
|
206
|
+
owners: [{ owner: owner1, weight: 9 }],
|
|
207
|
+
}),
|
|
208
|
+
).toBe(true)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test('accepts the maximum uint8 threshold', () => {
|
|
212
|
+
expect(
|
|
213
|
+
MultisigConfig.validate({
|
|
214
|
+
threshold: MultisigConfig.maxThreshold,
|
|
215
|
+
owners: [{ owner: owner1, weight: MultisigConfig.maxThreshold }],
|
|
216
|
+
}),
|
|
217
|
+
).toBe(true)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
test('threshold exceeds uint8 maximum', () => {
|
|
203
221
|
expect(
|
|
204
222
|
MultisigConfig.validate({
|
|
205
223
|
threshold: MultisigConfig.maxThreshold + 1,
|
|
206
|
-
owners: [{ owner: owner1, weight: MultisigConfig.maxThreshold
|
|
224
|
+
owners: [{ owner: owner1, weight: MultisigConfig.maxThreshold }],
|
|
207
225
|
}),
|
|
208
226
|
).toBe(false)
|
|
209
227
|
})
|
|
@@ -226,6 +244,22 @@ describe('assert / validate', () => {
|
|
|
226
244
|
).toBe(false)
|
|
227
245
|
})
|
|
228
246
|
|
|
247
|
+
test('threshold cannot require more than eight owners', () => {
|
|
248
|
+
const owners = Array.from({ length: 9 }, (_, i) => ({
|
|
249
|
+
owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
|
|
250
|
+
weight: 1,
|
|
251
|
+
}))
|
|
252
|
+
expect(MultisigConfig.validate({ threshold: 9, owners })).toBe(false)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
test('threshold can be reached by eight of more than eight owners', () => {
|
|
256
|
+
const owners = Array.from({ length: 9 }, (_, i) => ({
|
|
257
|
+
owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
|
|
258
|
+
weight: i === 0 ? 2 : 1,
|
|
259
|
+
}))
|
|
260
|
+
expect(MultisigConfig.validate({ threshold: 9, owners })).toBe(true)
|
|
261
|
+
})
|
|
262
|
+
|
|
229
263
|
test('total weight exceeds u8 max', () => {
|
|
230
264
|
expect(
|
|
231
265
|
MultisigConfig.validate({
|
package/tempo/MultisigConfig.ts
CHANGED
|
@@ -6,13 +6,13 @@ import * as Hex from '../core/Hex.js'
|
|
|
6
6
|
import type { Compute, OneOf } from '../core/internal/types.js'
|
|
7
7
|
|
|
8
8
|
/** Maximum number of owners allowed in a native multisig config. */
|
|
9
|
-
export const maxOwners =
|
|
9
|
+
export const maxOwners = 48
|
|
10
10
|
|
|
11
11
|
/** Maximum threshold accepted by a native multisig config. */
|
|
12
|
-
export const maxThreshold =
|
|
12
|
+
export const maxThreshold = 0xff
|
|
13
13
|
|
|
14
14
|
/** Maximum number of owner approvals in a native multisig signature. */
|
|
15
|
-
export const maxSignatures =
|
|
15
|
+
export const maxSignatures = 8
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Maximum number of native multisig signatures in one nested authorization
|
|
@@ -72,7 +72,8 @@ export type Tuple = readonly [
|
|
|
72
72
|
* Mirrors the Tempo `InitMultisig::validate` rules: owners non-empty and
|
|
73
73
|
* `<= maxOwners`, strictly ascending unique nonzero owner addresses, nonzero
|
|
74
74
|
* integer owner weights, integer `threshold` between `1` and `maxThreshold`,
|
|
75
|
-
* total weight `<= 255` (u8 max), and
|
|
75
|
+
* total weight `<= 255` (u8 max), and a threshold reachable by at most
|
|
76
|
+
* `maxSignatures` owners.
|
|
76
77
|
*
|
|
77
78
|
* @example
|
|
78
79
|
* ```ts twoslash
|
|
@@ -105,6 +106,7 @@ export function assert<numberType = number>(config: Config<numberType>): void {
|
|
|
105
106
|
throw new InvalidConfigError({ reason: 'threshold exceeds max threshold' })
|
|
106
107
|
|
|
107
108
|
let totalWeight = 0
|
|
109
|
+
const weights: number[] = []
|
|
108
110
|
let previous: bigint | undefined
|
|
109
111
|
for (const owner of owners) {
|
|
110
112
|
if (!Address.validate(owner.owner) || Hex.toBigInt(owner.owner) === 0n)
|
|
@@ -123,7 +125,9 @@ export function assert<numberType = number>(config: Config<numberType>): void {
|
|
|
123
125
|
})
|
|
124
126
|
previous = current
|
|
125
127
|
|
|
126
|
-
|
|
128
|
+
const weight = Number(owner.weight)
|
|
129
|
+
totalWeight += weight
|
|
130
|
+
weights.push(weight)
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
if (totalWeight > 0xff)
|
|
@@ -134,6 +138,15 @@ export function assert<numberType = number>(config: Config<numberType>): void {
|
|
|
134
138
|
throw new InvalidConfigError({
|
|
135
139
|
reason: 'threshold exceeds total owner weight',
|
|
136
140
|
})
|
|
141
|
+
|
|
142
|
+
const reachableWeight = weights
|
|
143
|
+
.sort((a, b) => b - a)
|
|
144
|
+
.slice(0, maxSignatures)
|
|
145
|
+
.reduce((sum, weight) => sum + weight, 0)
|
|
146
|
+
if (Number(threshold) > reachableWeight)
|
|
147
|
+
throw new InvalidConfigError({
|
|
148
|
+
reason: `threshold exceeds weight reachable by ${maxSignatures} owner signatures`,
|
|
149
|
+
})
|
|
137
150
|
}
|
|
138
151
|
|
|
139
152
|
export declare namespace assert {
|
package/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** @internal */
|
|
2
|
-
export const version = '0.14.
|
|
2
|
+
export const version = '0.14.35'
|