@sig-net/midnight 0.0.9 → 0.11.0
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/README.md +254 -112
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/ecdsa-attestation.d.ts +53 -1
- package/dist/ecdsa-attestation.d.ts.map +1 -1
- package/dist/ecdsa-attestation.js +96 -4
- package/dist/ecdsa-attestation.js.map +1 -1
- package/dist/epsilon-derivation.d.ts +8 -8
- package/dist/epsilon-derivation.d.ts.map +1 -1
- package/dist/epsilon-derivation.js +15 -17
- package/dist/epsilon-derivation.js.map +1 -1
- package/dist/index.d.ts +3 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -13
- package/dist/index.js.map +1 -1
- package/dist/managed/compiler/contract-info.json +76 -24
- package/dist/managed/compiler/contract-manifest.json +8 -8
- package/dist/managed/contract/index.d.ts +18 -10
- package/dist/managed/contract/index.js +218 -103
- package/dist/managed/contract/index.js.map +1 -1
- package/dist/signature-requests-state-reader.d.ts +2 -2
- package/dist/signature-requests-state-reader.d.ts.map +1 -1
- package/dist/signature-requests-state-reader.js +32 -15
- package/dist/signature-requests-state-reader.js.map +1 -1
- package/dist/signature-response-verification.d.ts +1 -1
- package/dist/signature-response-verification.js +5 -5
- package/dist/signature-response-verification.js.map +1 -1
- package/dist/signet-contract-state-reader.d.ts +49 -30
- package/dist/signet-contract-state-reader.d.ts.map +1 -1
- package/dist/signet-contract-state-reader.js +55 -35
- package/dist/signet-contract-state-reader.js.map +1 -1
- package/dist/signet-evtype2tx-requests.d.ts +65 -43
- package/dist/signet-evtype2tx-requests.d.ts.map +1 -1
- package/dist/signet-evtype2tx-requests.js +77 -78
- package/dist/signet-evtype2tx-requests.js.map +1 -1
- package/dist/signet-request-response-reader.d.ts +3 -3
- package/dist/signet-request-response-reader.js +9 -9
- package/dist/signet-request-response-reader.js.map +1 -1
- package/dist/signet-requests.d.ts +4 -4
- package/dist/signet-requests.js +3 -3
- package/dist/signet-requests.js.map +1 -1
- package/package.json +1 -1
- package/src/Signet.compact +122 -72
- package/src/circuits.compact +10 -27
package/README.md
CHANGED
|
@@ -2,146 +2,288 @@
|
|
|
2
2
|
|
|
3
3
|
The client-agnostic [Sig Network](https://sig.network) protocol library for the [Midnight blockchain](https://midnight.network), and the seed of a future signet.js Midnight adapter. It carries everything a Midnight contract or off-chain client needs to speak the signet protocol (the MPC's [sign bidirectional flow](https://docs.sig.network/architecture/sign-bidirectional)) without binding to any particular contract.
|
|
4
4
|
|
|
5
|
+
> ## 🚧 Under Construction 🚧
|
|
6
|
+
>
|
|
7
|
+
> This Sig Network Midnight integration is still under construction.
|
|
8
|
+
> Use at your own risk and expect rapid iteration.
|
|
9
|
+
|
|
5
10
|
## What is in it
|
|
6
11
|
|
|
7
12
|
- **Shared Compact modules**: the package ships its `.compact` sources, so contracts import them directly:
|
|
8
13
|
```compact
|
|
9
14
|
import "@sig-net/midnight/src/Signet";
|
|
10
15
|
```
|
|
11
|
-
- **Compiled pure circuits** (`pureCircuits`): the executable reference implementation of the client-agnostic circuits (
|
|
16
|
+
- **Compiled pure circuits** (`pureCircuits`): the executable reference implementation of the client-agnostic circuits (the attestation digest, response verification, the deploy-time key pin, the notification packer, the ABI word builders and readers). Off-chain code calls these compiled artefacts instead of re-porting the algorithms, so it always agrees with what the contracts prove.
|
|
12
17
|
- **TypeScript twins of the wire structs** and signet request-id computation.
|
|
13
18
|
- **State readers, request feed and resolver**: poll the signet contract for pending requests and their signature / remote-execution responses.
|
|
14
|
-
- **Crypto helpers**: epsilon derivation and
|
|
19
|
+
- **Crypto helpers**: epsilon derivation and ECDSA attestation verification.
|
|
20
|
+
|
|
21
|
+
Where signet.js already covers something (for example EVM transaction preparation or address derivation), import it from there. Only what is Midnight-specific lives here.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
npm install @sig-net/midnight
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Sign bidirectional flow
|
|
30
|
+
|
|
31
|
+
The flow comprises 5 steps:
|
|
15
32
|
|
|
16
|
-
|
|
33
|
+
1. Client calls a contract on Midnight which requests a signature for a transaction destined for a foreign chain. The signature is made with a key derived for the requesting contract (see [Derived keys](#derived-keys)).
|
|
34
|
+
2. The Sig Network MPC honours the request, generating the transaction signature and posting it back to Midnight.
|
|
35
|
+
3. Client extracts the signature, using it to submit the signed transaction to the foreign chain.
|
|
36
|
+
4. The Sig Network MPC observes the foreign transaction and posts the output of the execution (signed) back to Midnight.
|
|
37
|
+
5. Client extracts the signed foreign execution output and submits it back to the Midnight contract, which verifies the MPC's signature over it in-circuit against the contract's own response key (see [Derived keys](#derived-keys)), completing the foreign transaction execution.
|
|
17
38
|
|
|
18
39
|
## Derived keys
|
|
19
40
|
|
|
20
|
-
Every key the MPC
|
|
41
|
+
Every key the MPC uses is derived for the requesting contract and a path. There are two kinds: the request signing key, whose path each contract chooses, and the response signing key, whose path is fixed by the protocol. Both key derivations are **scoped by the address** of the requesting contract.
|
|
21
42
|
|
|
22
|
-
|
|
43
|
+
### Request signing key
|
|
23
44
|
|
|
24
|
-
The
|
|
45
|
+
The key the MPC signs requested foreign transactions with:
|
|
25
46
|
|
|
26
|
-
|
|
47
|
+
`requestSigningKey = f(mpcRootKey[keyVersion], contractAddress, path)`
|
|
27
48
|
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
The path is 32 opaque bytes of the contract's choosing (e.g. a fixed literal for a contract-owned account like "vault", or a hash of a caller's secret for per-user accounts). There are no format requirements. The contract address is always part of the derivation, so no contract can reach another contract's derived keys.
|
|
50
|
+
|
|
51
|
+
### Response key
|
|
52
|
+
|
|
53
|
+
The key the MPC signs foreign execution outputs with when posting them back to Midnight:
|
|
54
|
+
|
|
55
|
+
`responseKey = f(mpcRootKey[keyVersion], contractAddress, "midnight response key")`
|
|
56
|
+
|
|
57
|
+
The same derivation, but with the path fixed to the literal `"midnight response key"`, giving each contract one well-known response key. A contract pins its own response key in its ledger after deploy and verifies every response against it in-circuit (step 5 of the flow above).
|
|
58
|
+
|
|
59
|
+
## Integrator guide
|
|
60
|
+
|
|
61
|
+
A signet-compliant client contract does four things: it stores its requests in a public `SignBidirectionalEventMap` in its own ledger, it pins its counterparties (the Signet singleton contract and its own MPC response key), it submits signature requests, and it verifies execution responses in-circuit. Concretely, the integration consists of:
|
|
62
|
+
|
|
63
|
+
- 3 once-off **setup** steps
|
|
64
|
+
- 5 per-request **runtime** steps that drive the full sign bidirectional flow
|
|
65
|
+
|
|
66
|
+
### Setup
|
|
67
|
+
|
|
68
|
+
Set up your contract for integration with the Sig Network MPC's sign bidirectional flow:
|
|
69
|
+
|
|
70
|
+
1. Import the Signet module at the top of your contract (resolved through `node_modules` via `COMPACT_PATH`):
|
|
71
|
+
|
|
72
|
+
```compact
|
|
73
|
+
import "@sig-net/midnight/src/Signet";
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then tell the compact compiler about the npm packages with its `COMPACT_PATH` environment variable at compile time:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
COMPACT_PATH=node_modules compact compile --feature-zkir-v3 src/my-contract.compact src/managed/my-contract
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Compile with the pinned toolchain (currently `compact update 0.33.0-rc.2`) and always pass `--feature-zkir-v3`: compiled output without it is not compatible with the ledger-9 matched stack (node, indexer, proof server).
|
|
83
|
+
|
|
84
|
+
2. Declare the required Sig Network protocol state in your ledger (plus recommended deployer identity and initialisation state). The event map can sit at ANY ledger field: each notification your contract registers names the field position holding it (runtime step 1), and the MPC reads the authenticated request from there.
|
|
85
|
+
|
|
86
|
+
```compact
|
|
87
|
+
// Required: Map of SignBidirectionalEvent signature requests, configured by transaction type.
|
|
88
|
+
// Configured and sized here for an EVM Type 2 transaction with
|
|
89
|
+
// <1 calldata word, 0 access-list entries, 0 storage keys> and
|
|
90
|
+
// 34-byte serialisation schemas.
|
|
91
|
+
export ledger signBidirectionalEventMap: SignBidirectionalEventMap<EvmType2TxParams<1, 0, 0>, 34, 34>;
|
|
92
|
+
|
|
93
|
+
// Required: The Signet singleton signer interface, set at deploy.
|
|
94
|
+
// Used to notify the MPC of events you add to your signBidirectionalEventMap.
|
|
95
|
+
sealed ledger signetSigner: SignetSigner;
|
|
96
|
+
|
|
97
|
+
// Required: This contract's MPC response key, set in step 3.
|
|
98
|
+
// Used to verify RespondBidirectionalEvents containing the serialised output of foreign chain execution.
|
|
99
|
+
export ledger mpcResponseKey: Secp256k1Point;
|
|
100
|
+
|
|
101
|
+
// Recommended: contract-local source of request nonces, so identical
|
|
102
|
+
// requests hash to distinct request ids. Nothing off-chain reads it.
|
|
103
|
+
export ledger signetRequestNonce: Counter;
|
|
104
|
+
|
|
105
|
+
// Recommended: used in step 3 to ensure initialisation runs only once.
|
|
106
|
+
export ledger initialised: Counter;
|
|
107
|
+
|
|
108
|
+
// Recommended: set on deploy, used in step 3 to ensure only the deployer may set the mpcResponseKey.
|
|
109
|
+
sealed ledger deployer: Bytes<32>;
|
|
110
|
+
|
|
111
|
+
// Recommended: supplies the deployer's identity secret from private state
|
|
112
|
+
// off-chain; only its commitment (below) ever reaches the ledger.
|
|
113
|
+
witness witnessDeployerSecretKey(): Bytes<32>;
|
|
114
|
+
|
|
115
|
+
// Recommended: the deployer identity commitment scheme. Exported so deploy
|
|
116
|
+
// tooling can compute the constructor argument by calling the compiled circuit.
|
|
117
|
+
export pure circuit calculateDeployerCommitment(sk: Bytes<32>): Bytes<32> {
|
|
118
|
+
return persistentHash<Vector<2, Bytes<32>>>([pad(32, "my-contract:deployer:"), sk]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Required: set signet contract and (recommended) deployer commitment on deployment.
|
|
122
|
+
constructor(signetContract: SignetSigner, deployerCommitment: Bytes<32>) {
|
|
123
|
+
signetSigner = disclose(signetContract);
|
|
124
|
+
deployer = disclose(deployerCommitment);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
3. Set the contract's MPC response key once, right after deploy. Deriving this key requires the address of the contract, which only exists after deploy (see [Response key](#response-key)):
|
|
129
|
+
|
|
130
|
+
```compact
|
|
131
|
+
export circuit initialise(responseKey: Secp256k1Point): [] {
|
|
132
|
+
// Recommended: confirm that only the deployer may initialise, and only once:
|
|
133
|
+
assert(deployer == calculateDeployerCommitment(witnessDeployerSecretKey()), "Not the deployer");
|
|
134
|
+
assert(initialised == 0, "Already initialised");
|
|
135
|
+
initialised.increment(1);
|
|
136
|
+
|
|
137
|
+
// Required: set MPC response key for verification of RespondBidirectionalEvents
|
|
138
|
+
mpcResponseKey = disclose(responseKey);
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Runtime
|
|
143
|
+
|
|
144
|
+
Each interaction with your contract that executes a transaction on a foreign chain runs these 5 steps.
|
|
145
|
+
|
|
146
|
+
Steps 1 and 5 are circuits on your contract, and steps 2 to 4 are off-chain client code built on the utilities in `@sig-net/midnight`.
|
|
147
|
+
|
|
148
|
+
The off-chain steps share one `SignetRequestResponseReader` over your contract / Signet singleton pair, and the expected signer of the requested transaction (the key the MPC derives for your contract and the request's path, see [Derived keys](#derived-keys)):
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { indexerPublicDataProvider } from "@midnight-ntwrk/midnight-js-indexer-public-data-provider";
|
|
152
|
+
import { deriveEvmAddress, SignetRequestResponseReader } from "@sig-net/midnight";
|
|
153
|
+
|
|
154
|
+
// SignetRequestResponseReader to poll for Signed Transactions and Signed RespondBidirectionalEvents
|
|
155
|
+
const reader = new SignetRequestResponseReader({
|
|
156
|
+
// Address of YOUR deployed contract
|
|
157
|
+
requesterContractAddress: myContractAddress,
|
|
158
|
+
|
|
159
|
+
// signBidirectionalEventMap's field position (Setup step 2)
|
|
160
|
+
requesterRequestsIndexField: 0,
|
|
161
|
+
|
|
162
|
+
// Address of the Signet singleton contract
|
|
163
|
+
signetContractAddress,
|
|
164
|
+
|
|
165
|
+
// Provider to index Midnight Blockchain
|
|
166
|
+
publicDataProvider: indexerPublicDataProvider({
|
|
167
|
+
queryURL: indexerUrl,
|
|
168
|
+
subscriptionURL: indexerWsUrl
|
|
169
|
+
}),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const expectedSigner = deriveEvmAddress(mpcRootPublicKey, myContractAddress, "my-path");
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
1. Store a signature request and notify the MPC via cross contract call. Build (or overwrite) every part of the transaction your contract enforces in-circuit, calldata above all (see [EVM Type 2 transactions and ABI calldata words](#evm-type-2-transactions-and-abi-calldata-words)); never pass caller input through unchecked:
|
|
176
|
+
|
|
177
|
+
```compact
|
|
178
|
+
// Construct SignBidirectionalEvent signature request and calculate its RequestId
|
|
179
|
+
const request = constructSignBidirectionalEvent<EvmType2TxParams<1, 0, 0>, 34, 34>(/* ... */);
|
|
180
|
+
const requestId = disclose(calculateRequestId<EvmType2TxParams<1, 0, 0>, 34, 34>(request));
|
|
181
|
+
|
|
182
|
+
// Store the signature request in your signBidirectionalEventMap for MPC to discover
|
|
183
|
+
signetRequestNonce.increment(1);
|
|
184
|
+
signBidirectionalEventMap.insert(requestId, disclose(request));
|
|
185
|
+
|
|
186
|
+
// Notify the MPC of the SignBidirectionalEvent and the location of your signBidirectionalEventMap.
|
|
187
|
+
// The location is 0 here based on the position of the declaration in Setup step 2.
|
|
188
|
+
signetSigner.signBidirectional(
|
|
189
|
+
requestId,
|
|
190
|
+
constructSignBidirectionalEventNotificationV1(kernel.self(), 0 as Uint<8>),
|
|
191
|
+
);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**NOTE:** `requestId` should be returned from the above circuit call so that it may be used in subsequent steps (or compute it off-chain with the `calculateRequestId` TS twin).
|
|
195
|
+
|
|
196
|
+
2. Poll the Signet singleton for the MPC's signature response. The response log is unauthenticated (anyone can post), so use the verifying getter: it only returns a post whose signature recovers to `expectedSigner` over the requested transaction's signing hash:
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
const { verified } = await reader.getVerifiedSignatureRespondedEvent(requestId, expectedSigner);
|
|
200
|
+
// verified === undefined: no valid response posted yet, poll again.
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
3. Construct the signed transaction and submit it to the foreign chain. The reader rebuilds the transaction from the request record on your ledger and attaches the verified MPC signature:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { JsonRpcProvider } from "ethers";
|
|
207
|
+
|
|
208
|
+
const signedTx = await reader.getSignedEvmTransaction(requestId, expectedSigner);
|
|
209
|
+
await new JsonRpcProvider(foreignChainRpcUrl).broadcastTransaction(signedTx.serialized);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
4. Poll the Signet singleton for the MPC's signed remote execution output (posted once the MPC observes the transaction execute on the foreign chain). Posts are stored unverified, so treat them as candidates: the authoritative check is your contract's verify circuit in step 5:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
const [respondBidirectionalEvent] = await reader.getRespondBidirectionalEvents(requestId);
|
|
216
|
+
// Empty array: not posted yet, poll again.
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
5. Deliver the response to your contract, which verifies it in-circuit against the response key pinned in Setup step 3 and consumes the request:
|
|
220
|
+
|
|
221
|
+
```compact
|
|
222
|
+
assert(
|
|
223
|
+
verifyRespondBidirectionalEvent(requestId, respondBidirectionalEvent, mpcResponseKey),
|
|
224
|
+
"Invalid attestation signature"
|
|
225
|
+
);
|
|
226
|
+
signBidirectionalEventMap.remove(requestId);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## EVM Type 2 transactions and ABI calldata words
|
|
230
|
+
|
|
231
|
+
An `EvmType2TxParams` request decomposes the EVM transaction into typed fields your contract can enforce field by field in-circuit. Its optional `calldata` is an `EvmCalldata<maxWords>`: the 4-byte function selector plus a list of 32-byte ABI words, per the [Solidity ABI spec](https://docs.soliditylang.org/en/latest/abi-spec.html). Slots past `noWords` are unused capacity and never reach the transaction.
|
|
232
|
+
|
|
233
|
+
Every word must be stored in canonical ABI form (big-endian). The MPC signs a transaction whose calldata is exactly `selector || words[0..noWords]`, byte for byte, so a word stored in any other form becomes a signed transaction calling the foreign contract with garbage arguments. Compact's integer casts are little-endian, so do not hand-roll the byte order: build every word with the module's helper circuits, and read words back with the matching readers.
|
|
234
|
+
|
|
235
|
+
| Solidity type | Build with | Read back with |
|
|
236
|
+
|---|---|---|
|
|
237
|
+
| `address` | `evmAddressAbiWord(addr: Bytes<20>)` | |
|
|
238
|
+
| unsigned integers up to `uint128` (amounts, ids) | `numericAbiWord(value: Uint<128>)` | `abiWordToUint128(word)` |
|
|
239
|
+
| `bool` | `boolAbiWord(value: Boolean)` | `abiWordToBool(word)` |
|
|
240
|
+
|
|
241
|
+
### Example: an ERC20 transfer
|
|
242
|
+
|
|
243
|
+
`transfer(address,uint256)`, selector `0xa9059cbb`, takes an address word and a numeric word:
|
|
244
|
+
|
|
245
|
+
```compact
|
|
246
|
+
const calldata = EvmCalldata<2> {
|
|
247
|
+
selector: Bytes[0xa9, 0x05, 0x9c, 0xbb],
|
|
248
|
+
noWords: 2 as Uint<16>,
|
|
249
|
+
words: [
|
|
250
|
+
evmAddressAbiWord(recipient), // address argument (Bytes<20>)
|
|
251
|
+
numericAbiWord(amount) // uint256 argument (from a Uint<128>)
|
|
252
|
+
]
|
|
253
|
+
};
|
|
30
254
|
```
|
|
31
255
|
|
|
32
|
-
|
|
256
|
+
### Example: a bool argument, and decoding a bool result
|
|
33
257
|
|
|
34
|
-
|
|
258
|
+
`setApprovalForAll(address,bool)`, selector `0xa22cb465`:
|
|
35
259
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
260
|
+
```compact
|
|
261
|
+
const calldata = EvmCalldata<2> {
|
|
262
|
+
selector: Bytes[0xa2, 0x2c, 0xb4, 0x65],
|
|
263
|
+
noWords: 2 as Uint<16>,
|
|
264
|
+
words: [
|
|
265
|
+
evmAddressAbiWord(operator),
|
|
266
|
+
boolAbiWord(true)
|
|
267
|
+
]
|
|
268
|
+
};
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The readers run the same rules in the other direction, rejecting any non-canonical word instead of silently truncating or coercing it. A `RespondBidirectionalEvent`'s `serializedOutput` is the ABI-encoded return data of the remote call, so a settle circuit can decode an ERC20 `transfer`'s `bool` return from the first output word:
|
|
40
272
|
|
|
41
273
|
```compact
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
import CompactStandardLibrary;
|
|
45
|
-
import "@sig-net/midnight/src/Signet";
|
|
46
|
-
|
|
47
|
-
// (1) The request index: the public map the MPC reads requests back from.
|
|
48
|
-
// It can sit at any ledger field; the notification in submitSignatureRequest
|
|
49
|
-
// names its position (0 in this contract). The counter is contract-local
|
|
50
|
-
// (it sources each request's nonce so identical requests get distinct ids);
|
|
51
|
-
// the MPC never reads it. The generic size parameters cap the EVM calldata
|
|
52
|
-
// words, access list entries and storage keys your requests may carry.
|
|
53
|
-
export ledger signetRequestsIndex: SignBidirectionalRequestIndex<EVMType2TxParams<1, 0, 0>>;
|
|
54
|
-
export ledger signetNonce: SignetNonce;
|
|
55
|
-
|
|
56
|
-
// (2) Pinned at deploy time.
|
|
57
|
-
export sealed ledger mpcPubKeyHash: Bytes<32>;
|
|
58
|
-
sealed ledger signetNotifier: SignetNotifier;
|
|
59
|
-
|
|
60
|
-
constructor(mpcPk: JubjubPoint, signetContract: SignetNotifier) {
|
|
61
|
-
mpcPubKeyHash = disclose(persistentHash<JubjubPoint>(mpcPk));
|
|
62
|
-
signetNotifier = disclose(signetContract);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// (3) Construct, store and announce a signature request.
|
|
66
|
-
export circuit submitSignatureRequest(evmNonce: Uint<64>, keyVersion: Uint<32>): [] {
|
|
67
|
-
// Decompose the EVM transaction to be signed. Build (or overwrite) every
|
|
68
|
-
// part your contract enforces in-circuit, calldata above all; never pass
|
|
69
|
-
// caller input through unchecked.
|
|
70
|
-
const txParams = EVMType2TxParams<1, 0, 0> {
|
|
71
|
-
to: pad(20, "recipient"),
|
|
72
|
-
chainId: 31337 as Uint<64>,
|
|
73
|
-
nonce: evmNonce,
|
|
74
|
-
gasLimit: 100000 as Uint<64>,
|
|
75
|
-
maxFeePerGas: 30000000000 as Uint<128>,
|
|
76
|
-
maxPriorityFeePerGas: 1000000000 as Uint<128>,
|
|
77
|
-
value: 0 as Uint<128>,
|
|
78
|
-
accessListEntryCount: 0,
|
|
79
|
-
accessList: [],
|
|
80
|
-
calldata: none<EVMCalldata<1>>()
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
// The canonical request record. This variant signs with the CONTRACT's own
|
|
84
|
-
// derived account (a path literal the contract fixes); use
|
|
85
|
-
// constructSignBidirectionalRequest instead for caller-identity requests,
|
|
86
|
-
// where the path is bound to the caller's identity commitment.
|
|
87
|
-
const schema = pad(128, "[{\"name\":\"success\",\"type\":\"bool\"}]");
|
|
88
|
-
const request = constructContractPathSignBidirectionalRequest<EVMType2TxParams<1, 0, 0>>(
|
|
89
|
-
signetNonce as Uint<64>, // contract-local request nonce
|
|
90
|
-
TxParamType.evmType2,
|
|
91
|
-
txParams,
|
|
92
|
-
pad(32, "eip155:31337"), // target chain (CAIP-2)
|
|
93
|
-
keyVersion, // MPC root-key version (>= 1)
|
|
94
|
-
pad(256, "my-treasury"), // contract-owned derivation path
|
|
95
|
-
pad(32, "ecdsa"),
|
|
96
|
-
pad(32, "ethereum"),
|
|
97
|
-
pad(64, ""),
|
|
98
|
-
schema,
|
|
99
|
-
schema
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
// Store the request in YOUR ledger under its id, then register the
|
|
103
|
-
// notification in the central signet contract's registry. The final
|
|
104
|
-
// argument is the ledger field position of signetRequestsIndex, which is
|
|
105
|
-
// how the MPC finds the index to read the request back from.
|
|
106
|
-
const requestId = disclose(calculateRequestId<EVMType2TxParams<1, 0, 0>>(request));
|
|
107
|
-
assert(!signetRequestsIndex.member(requestId), "Request already exists");
|
|
108
|
-
signetNonce.increment(1);
|
|
109
|
-
signetRequestsIndex.insert(requestId, disclose(request));
|
|
110
|
-
signetNotifier.notifyBidirectionalSignatureRequest(requestId,
|
|
111
|
-
constructSignBidirectionalNotificationV1(kernel.self(), requestId as Bytes<32>, 0 as Uint<8>));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// (4) Verify the MPC's Schnorr-signed attestation of the remote execution
|
|
115
|
-
// and consume the request (double-verify protection).
|
|
116
|
-
export circuit verifyResponse(requestId: RequestId, respondBidirectional: RespondBidirectional): [] {
|
|
117
|
-
const rid = disclose(requestId);
|
|
118
|
-
assert(
|
|
119
|
-
persistentHash<JubjubPoint>(respondBidirectional.pk) == mpcPubKeyHash,
|
|
120
|
-
"Unauthorized: attestation pk is not the MPC key"
|
|
121
|
-
);
|
|
122
|
-
assert(
|
|
123
|
-
jubjubSchnorrVerify<4>(
|
|
124
|
-
signetAttestationMessage(rid, respondBidirectional.serializedOutput, respondBidirectional.outputLen),
|
|
125
|
-
JubjubSchnorrSignature {
|
|
126
|
-
announcement: respondBidirectional.announcement,
|
|
127
|
-
response: respondBidirectional.response,
|
|
128
|
-
},
|
|
129
|
-
respondBidirectional.pk
|
|
130
|
-
),
|
|
131
|
-
"Invalid attestation signature"
|
|
132
|
-
);
|
|
133
|
-
assert(signetRequestsIndex.member(rid), "Request not found");
|
|
134
|
-
signetRequestsIndex.remove(rid);
|
|
135
|
-
}
|
|
274
|
+
const success = abiWordToBool(slice<32>(respondBidirectionalEvent.serializedOutput, 0));
|
|
275
|
+
assert(success, "Remote transfer failed");
|
|
136
276
|
```
|
|
137
277
|
|
|
138
|
-
|
|
278
|
+
The same builders and readers exist as TypeScript twins under identical names, for composing expected words off-chain (UIs, expected-record builders, tests). They are kept in lockstep with the compiled circuits by this package's test suite.
|
|
279
|
+
|
|
280
|
+
## More examples
|
|
139
281
|
|
|
140
|
-
|
|
282
|
+
For full integration examples (such as an ERC20 cross chain vault) see the [`sig-net/midnight-examples`](https://github.com/sig-net/midnight-examples) repository.
|
|
141
283
|
|
|
142
284
|
## Related packages
|
|
143
285
|
|
|
144
286
|
- [`@sig-net/midnight-contract`](https://www.npmjs.com/package/@sig-net/midnight-contract): the central signet contract this library reads from.
|
|
145
287
|
- [`@sig-net/midnight-contract-deploy`](https://www.npmjs.com/package/@sig-net/midnight-contract-deploy): deploy tooling for that contract plus generic Midnight deploy/wallet plumbing.
|
|
146
288
|
|
|
147
|
-
Developed in [sig-net/midnight-integration](https://github.com/sig-net/midnight-integration)
|
|
289
|
+
Developed in [sig-net/midnight-integration](https://github.com/sig-net/midnight-integration).
|
package/dist/constants.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export declare const CAIP2_ID_BYTES = 32;
|
|
3
3
|
/** Width of `SignBidirectionalEvent.params` (`Bytes<64>`). */
|
|
4
4
|
export declare const MPC_PARAMS_BYTES = 64;
|
|
5
|
-
/** Width of `
|
|
5
|
+
/** Width of `EvmCalldata.selector` (`Bytes<4>`) — the literal first 4 calldata bytes. */
|
|
6
6
|
export declare const SELECTOR_BYTES = 4;
|
|
7
7
|
/** Width of `RespondBidirectionalEvent.serializedOutput` (`Bytes<128>`). */
|
|
8
8
|
export declare const SERIALIZED_OUTPUT_BYTES = 128;
|
package/dist/constants.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
export const CAIP2_ID_BYTES = 32;
|
|
12
12
|
/** Width of `SignBidirectionalEvent.params` (`Bytes<64>`). */
|
|
13
13
|
export const MPC_PARAMS_BYTES = 64;
|
|
14
|
-
/** Width of `
|
|
14
|
+
/** Width of `EvmCalldata.selector` (`Bytes<4>`) — the literal first 4 calldata bytes. */
|
|
15
15
|
export const SELECTOR_BYTES = 4;
|
|
16
16
|
/** Width of `RespondBidirectionalEvent.serializedOutput` (`Bytes<128>`). */
|
|
17
17
|
export const SERIALIZED_OUTPUT_BYTES = 128;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Secp256k1Point } from "@midnight-ntwrk/compact-runtime";
|
|
2
|
+
import type { MpcSignature } from "./signet-contract-state-reader.ts";
|
|
2
3
|
export type { Secp256k1Point } from "@midnight-ntwrk/compact-runtime";
|
|
3
4
|
/** secp256k1 curve (group) order n. */
|
|
4
5
|
export declare const SECP256K1_ORDER: bigint;
|
|
@@ -23,11 +24,30 @@ export declare function bytesToBigint(bytes: Uint8Array): bigint;
|
|
|
23
24
|
* @returns The 32-byte little-endian encoding.
|
|
24
25
|
*/
|
|
25
26
|
export declare function bigintToBytes32(n: bigint): Uint8Array;
|
|
27
|
+
/**
|
|
28
|
+
* Convert big-endian bytes to a bigint (SEC1 coordinate order, the byte
|
|
29
|
+
* order the respond events' stored signature uses).
|
|
30
|
+
*
|
|
31
|
+
* @param bytes - Big-endian byte array.
|
|
32
|
+
* @returns The decoded non-negative integer.
|
|
33
|
+
*/
|
|
34
|
+
export declare function bytesToBigintBE(bytes: Uint8Array): bigint;
|
|
35
|
+
/**
|
|
36
|
+
* Convert a bigint to exactly 32 BIG-endian bytes (SEC1 scalar/coordinate
|
|
37
|
+
* order, also the ABI word encoding) — the big-endian counterpart of
|
|
38
|
+
* {@link bigintToBytes32} and the inverse of {@link bytesToBigintBE}.
|
|
39
|
+
*
|
|
40
|
+
* @param value - The non-negative integer to encode.
|
|
41
|
+
* @returns The 32-byte big-endian encoding.
|
|
42
|
+
* @throws Error if the value is negative or does not fit 32 bytes.
|
|
43
|
+
*/
|
|
44
|
+
export declare function bigintToBytes32BE(value: bigint): Uint8Array;
|
|
26
45
|
/**
|
|
27
46
|
* An ECDSA signature over the attestation digest, in the MPC's canonical
|
|
28
47
|
* `Signature { big_r, s, recovery_id }` spirit with `r` already reduced to
|
|
29
48
|
* the scalar `big_r.x mod n` — the form Compact's `Secp256k1EcdsaSignature`
|
|
30
|
-
* takes.
|
|
49
|
+
* takes. Build the stored form for posting in a respond event with
|
|
50
|
+
* {@link ecdsaSignatureToMpcSignature}.
|
|
31
51
|
*/
|
|
32
52
|
export interface EcdsaSignature {
|
|
33
53
|
/** Signature scalar r (= R.x mod n). */
|
|
@@ -49,6 +69,38 @@ export interface EcdsaSignature {
|
|
|
49
69
|
* @returns The signature with its recovery id.
|
|
50
70
|
*/
|
|
51
71
|
export declare function signAttestationDigest(digest: Uint8Array, secretKey: Uint8Array): EcdsaSignature;
|
|
72
|
+
/**
|
|
73
|
+
* Build the stored-form signature both respond events carry from a
|
|
74
|
+
* scalar-form one: R is reconstructed by decompressing the curve point with
|
|
75
|
+
* x = `r` and the parity `recoveryId` names — undoing the compression an
|
|
76
|
+
* `r || s || v` signature performs. For MPC-side posters (the fakenet
|
|
77
|
+
* signer, tests). The negligible caveat: an R.x that overflowed the curve
|
|
78
|
+
* order cannot be told apart from its reduced twin, so reconstruction picks
|
|
79
|
+
* the x equal to `r` itself.
|
|
80
|
+
*
|
|
81
|
+
* @param signature - The scalar-form signature (the signer's output shape).
|
|
82
|
+
* @returns The stored-form signature: R as a full point, big-endian bytes.
|
|
83
|
+
* @throws Error if the recovery id is not 0 or 1, or `r` is not the x
|
|
84
|
+
* coordinate of a secp256k1 point.
|
|
85
|
+
*/
|
|
86
|
+
export declare function ecdsaSignatureToMpcSignature(signature: EcdsaSignature): MpcSignature;
|
|
87
|
+
/**
|
|
88
|
+
* Read a stored-form signature back into scalar form — the inverse of
|
|
89
|
+
* {@link ecdsaSignatureToMpcSignature}, for off-chain consumers building a
|
|
90
|
+
* transaction from the response. In-circuit verification does not need this:
|
|
91
|
+
* `verifyRespondBidirectionalEvent` takes the stored record as-is. `r` comes
|
|
92
|
+
* out as `bigR.x` reduced mod the curve order. Rejects records that do not
|
|
93
|
+
* even hold the shape, since posts are unauthenticated and a malformed
|
|
94
|
+
* record is garbage, not a signature. `bigR.y` is NOT checked against the
|
|
95
|
+
* curve or the recovery id: signature verification is the authority, not
|
|
96
|
+
* this decoder.
|
|
97
|
+
*
|
|
98
|
+
* @param signature - The stored-form signature as posted.
|
|
99
|
+
* @returns The scalar-form signature.
|
|
100
|
+
* @throws Error if a component has the wrong byte length or the recovery id
|
|
101
|
+
* is not 0 or 1.
|
|
102
|
+
*/
|
|
103
|
+
export declare function mpcSignatureToEcdsaSignature(signature: MpcSignature): EcdsaSignature;
|
|
52
104
|
/**
|
|
53
105
|
* Parse a secp256k1 public key from SEC1 hex (compressed or uncompressed,
|
|
54
106
|
* optional `0x` prefix) into the Compact runtime's `Secp256k1Point` shape —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ecdsa-attestation.d.ts","sourceRoot":"","sources":["../src/ecdsa-attestation.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAItE,YAAY,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,uCAAuC;AACvC,eAAO,MAAM,eAAe,QAA2B,CAAC;AAExD,uEAAuE;AACvE,eAAO,MAAM,SAAS,iFAAiF,CAAC;AAExG;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAMvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAQrD;
|
|
1
|
+
{"version":3,"file":"ecdsa-attestation.d.ts","sourceRoot":"","sources":["../src/ecdsa-attestation.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAItE,YAAY,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,uCAAuC;AACvC,eAAO,MAAM,eAAe,QAA2B,CAAC;AAExD,uEAAuE;AACvE,eAAO,MAAM,SAAS,iFAAiF,CAAC;AAExG;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAMvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAQrD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAMzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAW3D;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,wCAAwC;IACxC,CAAC,EAAE,MAAM,CAAC;IACV,2DAA2D;IAC3D,CAAC,EAAE,MAAM,CAAC;IACV,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,UAAU,GACpB,cAAc,CAiBhB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,cAAc,GAAG,YAAY,CAmBpF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,YAAY,GAAG,cAAc,CAapF;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAcrE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAGtE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,UAAU,GAAG,cAAc,CAO1E"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// secp256k1 ECDSA helpers: the TS side of the respond-bidirectional
|
|
2
2
|
// attestation flow in Signet.compact — SIGNING (which needs the secret
|
|
3
3
|
// scalar, so it cannot be a circuit), key parsing/formatting, and the
|
|
4
|
-
// byte-order conversions between noble's bigints and the
|
|
5
|
-
//
|
|
4
|
+
// byte-order conversions between noble's bigints and the little-endian
|
|
5
|
+
// scalar bytes Compact's casts read. Everything provable stays in Compact: the
|
|
6
6
|
// attestation digest is the COMPILED `pureCircuits.signetAttestationDigest`,
|
|
7
7
|
// verification is `pureCircuits.verifyRespondBidirectionalEvent` (the same
|
|
8
8
|
// check client contracts run in-circuit), and the deploy-time key pin is
|
|
@@ -48,14 +48,41 @@ export function bigintToBytes32(n) {
|
|
|
48
48
|
}
|
|
49
49
|
return buf;
|
|
50
50
|
}
|
|
51
|
-
/**
|
|
52
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Convert big-endian bytes to a bigint (SEC1 coordinate order, the byte
|
|
53
|
+
* order the respond events' stored signature uses).
|
|
54
|
+
*
|
|
55
|
+
* @param bytes - Big-endian byte array.
|
|
56
|
+
* @returns The decoded non-negative integer.
|
|
57
|
+
*/
|
|
58
|
+
export function bytesToBigintBE(bytes) {
|
|
53
59
|
let value = 0n;
|
|
54
60
|
for (const byte of bytes) {
|
|
55
61
|
value = (value << 8n) | BigInt(byte);
|
|
56
62
|
}
|
|
57
63
|
return value;
|
|
58
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Convert a bigint to exactly 32 BIG-endian bytes (SEC1 scalar/coordinate
|
|
67
|
+
* order, also the ABI word encoding) — the big-endian counterpart of
|
|
68
|
+
* {@link bigintToBytes32} and the inverse of {@link bytesToBigintBE}.
|
|
69
|
+
*
|
|
70
|
+
* @param value - The non-negative integer to encode.
|
|
71
|
+
* @returns The 32-byte big-endian encoding.
|
|
72
|
+
* @throws Error if the value is negative or does not fit 32 bytes.
|
|
73
|
+
*/
|
|
74
|
+
export function bigintToBytes32BE(value) {
|
|
75
|
+
if (value < 0n || value >= 1n << 256n) {
|
|
76
|
+
throw new Error(`value does not fit 32 big-endian bytes: ${value}`);
|
|
77
|
+
}
|
|
78
|
+
const out = new Uint8Array(32);
|
|
79
|
+
let v = value;
|
|
80
|
+
for (let i = 31; i >= 0; i--) {
|
|
81
|
+
out[i] = Number(v & 0xffn);
|
|
82
|
+
v >>= 8n;
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
59
86
|
/**
|
|
60
87
|
* ECDSA-sign a 32-byte digest with a secp256k1 secret key, exactly as the
|
|
61
88
|
* MPC signs the attestation digest: RFC 6979 deterministic, low-s, the digest
|
|
@@ -85,6 +112,71 @@ export function signAttestationDigest(digest, secretKey) {
|
|
|
85
112
|
/* v8 ignore next: unreachable for a signature this function just produced */
|
|
86
113
|
throw new Error("signature does not recover to its own public key");
|
|
87
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Build the stored-form signature both respond events carry from a
|
|
117
|
+
* scalar-form one: R is reconstructed by decompressing the curve point with
|
|
118
|
+
* x = `r` and the parity `recoveryId` names — undoing the compression an
|
|
119
|
+
* `r || s || v` signature performs. For MPC-side posters (the fakenet
|
|
120
|
+
* signer, tests). The negligible caveat: an R.x that overflowed the curve
|
|
121
|
+
* order cannot be told apart from its reduced twin, so reconstruction picks
|
|
122
|
+
* the x equal to `r` itself.
|
|
123
|
+
*
|
|
124
|
+
* @param signature - The scalar-form signature (the signer's output shape).
|
|
125
|
+
* @returns The stored-form signature: R as a full point, big-endian bytes.
|
|
126
|
+
* @throws Error if the recovery id is not 0 or 1, or `r` is not the x
|
|
127
|
+
* coordinate of a secp256k1 point.
|
|
128
|
+
*/
|
|
129
|
+
export function ecdsaSignatureToMpcSignature(signature) {
|
|
130
|
+
if (signature.recoveryId !== 0 && signature.recoveryId !== 1) {
|
|
131
|
+
throw new Error(`expected a recovery id of 0 or 1, got ${signature.recoveryId}`);
|
|
132
|
+
}
|
|
133
|
+
// SEC1 compressed form: parity prefix (02 even, 03 odd) || x big-endian.
|
|
134
|
+
const parityPrefix = signature.recoveryId === 0 ? "02" : "03";
|
|
135
|
+
const xHex = signature.r.toString(16).padStart(64, "0");
|
|
136
|
+
let point;
|
|
137
|
+
try {
|
|
138
|
+
point = secp256k1.Point.fromHex(`${parityPrefix}${xHex}`);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
throw new Error(`signature r is not the x coordinate of a secp256k1 point (${String(error)})`);
|
|
142
|
+
}
|
|
143
|
+
const uncompressed = point.toBytes(false); // 0x04 || x || y
|
|
144
|
+
return {
|
|
145
|
+
bigR: { x: uncompressed.slice(1, 33), y: uncompressed.slice(33, 65) },
|
|
146
|
+
s: bigintToBytes32BE(signature.s),
|
|
147
|
+
recoveryId: BigInt(signature.recoveryId),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Read a stored-form signature back into scalar form — the inverse of
|
|
152
|
+
* {@link ecdsaSignatureToMpcSignature}, for off-chain consumers building a
|
|
153
|
+
* transaction from the response. In-circuit verification does not need this:
|
|
154
|
+
* `verifyRespondBidirectionalEvent` takes the stored record as-is. `r` comes
|
|
155
|
+
* out as `bigR.x` reduced mod the curve order. Rejects records that do not
|
|
156
|
+
* even hold the shape, since posts are unauthenticated and a malformed
|
|
157
|
+
* record is garbage, not a signature. `bigR.y` is NOT checked against the
|
|
158
|
+
* curve or the recovery id: signature verification is the authority, not
|
|
159
|
+
* this decoder.
|
|
160
|
+
*
|
|
161
|
+
* @param signature - The stored-form signature as posted.
|
|
162
|
+
* @returns The scalar-form signature.
|
|
163
|
+
* @throws Error if a component has the wrong byte length or the recovery id
|
|
164
|
+
* is not 0 or 1.
|
|
165
|
+
*/
|
|
166
|
+
export function mpcSignatureToEcdsaSignature(signature) {
|
|
167
|
+
const { bigR, s, recoveryId } = signature;
|
|
168
|
+
if (bigR.x.length !== 32 || bigR.y.length !== 32 || s.length !== 32) {
|
|
169
|
+
throw new Error("expected 32-byte bigR.x/bigR.y/s in a stored signature");
|
|
170
|
+
}
|
|
171
|
+
if (recoveryId !== 0n && recoveryId !== 1n) {
|
|
172
|
+
throw new Error(`expected a recovery id of 0 or 1, got ${recoveryId}`);
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
r: bytesToBigintBE(bigR.x) % SECP256K1_ORDER,
|
|
176
|
+
s: bytesToBigintBE(s),
|
|
177
|
+
recoveryId: Number(recoveryId),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
88
180
|
/**
|
|
89
181
|
* Parse a secp256k1 public key from SEC1 hex (compressed or uncompressed,
|
|
90
182
|
* optional `0x` prefix) into the Compact runtime's `Secp256k1Point` shape —
|