@frontiercompute/zcash-ika 0.1.0 → 0.3.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 +126 -92
- package/dist/hybrid.d.ts +119 -0
- package/dist/hybrid.js +148 -0
- package/dist/index.d.ts +117 -65
- package/dist/index.js +671 -88
- package/dist/tx-builder.d.ts +67 -0
- package/dist/tx-builder.js +534 -0
- package/package.json +32 -4
- package/dist/test-dkg.d.ts +0 -17
- package/dist/test-dkg.js +0 -150
- package/src/index.ts +0 -338
- package/src/test-dkg.ts +0 -199
- package/tsconfig.json +0 -13
package/README.md
CHANGED
|
@@ -1,46 +1,31 @@
|
|
|
1
1
|
# zcash-ika
|
|
2
2
|
|
|
3
|
-
Split-key custody for Zcash and
|
|
3
|
+
Split-key custody for Zcash, Bitcoin, and EVM chains. The private key never exists whole. Spend policy enforced on-chain. Every action attested to Zcash.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@frontiercompute/zcash-ika)
|
|
6
6
|
|
|
7
|
-
## What this
|
|
7
|
+
## What this does
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
One secp256k1 dWallet on [Ika's 2PC-MPC network](https://ika.xyz) signs for three chain families. Your device holds half the key. Ika's nodes hold the other half. Neither can spend alone.
|
|
10
10
|
|
|
11
|
-
**
|
|
11
|
+
- **Spend policy** enforced by Sui Move contract (per-tx limits, daily caps, recipient whitelist, emergency freeze)
|
|
12
|
+
- **Transparent TX builder** for Zcash v5 transactions (ZIP 244 sighash, P2PKH, UTXO selection)
|
|
13
|
+
- **Attestation** of every operation to Zcash mainnet via [ZAP1](https://pay.frontiercompute.io)
|
|
14
|
+
- **5-chain verification** of attestation proofs (Arbitrum, Base, Hyperliquid, Solana, NEAR)
|
|
12
15
|
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
### AI Agent Custody
|
|
16
|
-
Your agent needs to spend money. Give it a split-key wallet instead of full access. The agent requests transactions, but can't override spending limits, daily caps, or approved recipient lists. If the agent gets compromised, the attacker gets half a key. Worthless.
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
npx @frontiercompute/zcash-mcp # 17 tools, any MCP client
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
### DAO Treasury
|
|
23
|
-
Multi-sig is 2003 technology. Split-key custody means the treasury key literally doesn't exist in one place. Policy lives in a Sui Move contract that no single party controls. Every spend attested to Zcash for the full audit trail, but balances stay shielded.
|
|
24
|
-
|
|
25
|
-
### Privacy Payroll
|
|
26
|
-
Pay contributors without publishing amounts on a block explorer. Shielded ZEC from an Orchard address, every payment attested via ZAP1 for compliance. The auditor sees proof of payment. Nobody else sees anything.
|
|
27
|
-
|
|
28
|
-
### Cross-Border Commerce
|
|
29
|
-
Hold shielded ZEC + stablecoins (USDC/USDT via secp256k1 on EVM chains) in one wallet. Same operator, same policy. Swap between them via NEAR Intents. Settlement is private. Compliance is provable.
|
|
30
|
-
|
|
31
|
-
### Compliance Without Exposure
|
|
32
|
-
ZAP1 attestations prove what happened without revealing what you hold. Bond deposits prove skin in the game. Policy verification proves you followed the rules. All on Zcash mainnet, all verifiable, nothing visible beyond what you choose to share.
|
|
33
|
-
|
|
34
|
-
## Signing parameters
|
|
16
|
+
## Chain support
|
|
35
17
|
|
|
36
18
|
| Chain | Curve | Algorithm | Hash | Status |
|
|
37
19
|
|-------|-------|-----------|------|--------|
|
|
38
|
-
| Zcash
|
|
39
|
-
| Bitcoin |
|
|
40
|
-
|
|
|
41
|
-
|
|
20
|
+
| Zcash transparent | secp256k1 | ECDSA | DoubleSHA256 | TX builder + signing live |
|
|
21
|
+
| Bitcoin | secp256k1 | ECDSA | DoubleSHA256 | Same dWallet, signing live |
|
|
22
|
+
| Ethereum/EVM | secp256k1 | ECDSA | KECCAK256 | Same dWallet, signing live |
|
|
23
|
+
|
|
24
|
+
One dWallet. Three chain families. Split custody on all of them.
|
|
42
25
|
|
|
43
|
-
|
|
26
|
+
## What does NOT work
|
|
27
|
+
|
|
28
|
+
**Zcash shielded (Orchard)** requires RedPallas on the Pallas curve. Ika supports secp256k1 and Ed25519, not Pallas. No path from Ika to Orchard signing today. For shielded, use the [embedded wallet](https://github.com/Frontier-Compute/zap1) which holds Orchard keys directly.
|
|
44
29
|
|
|
45
30
|
## Install
|
|
46
31
|
|
|
@@ -52,102 +37,151 @@ npm install @frontiercompute/zcash-ika
|
|
|
52
37
|
|
|
53
38
|
```typescript
|
|
54
39
|
import {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
spendBitcoin,
|
|
40
|
+
createWallet,
|
|
41
|
+
sign,
|
|
58
42
|
setPolicy,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
43
|
+
spendTransparent,
|
|
44
|
+
checkPolicy,
|
|
45
|
+
deriveZcashAddress,
|
|
62
46
|
} from "@frontiercompute/zcash-ika";
|
|
63
47
|
|
|
64
48
|
const config = {
|
|
65
|
-
network: "
|
|
66
|
-
|
|
49
|
+
network: "testnet",
|
|
50
|
+
suiPrivateKey: "suiprivkey1...",
|
|
67
51
|
zap1ApiUrl: "https://pay.frontiercompute.io",
|
|
68
|
-
zap1ApiKey: "your-key",
|
|
69
52
|
};
|
|
70
53
|
|
|
71
|
-
// Create
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
54
|
+
// 1. Create split-key wallet
|
|
55
|
+
const wallet = await createWallet(config);
|
|
56
|
+
console.log("dWallet:", wallet.id);
|
|
57
|
+
console.log("t-addr:", wallet.address);
|
|
58
|
+
console.log("Save this seed:", wallet.encryptionSeed);
|
|
59
|
+
|
|
60
|
+
// 2. Set spend policy (Sui Move contract)
|
|
61
|
+
const policy = await setPolicy(config, wallet.id, {
|
|
62
|
+
maxPerTx: 100_000_000, // 1 ZEC max per transaction
|
|
63
|
+
maxDaily: 500_000_000, // 5 ZEC daily cap
|
|
64
|
+
allowedRecipients: [], // empty = any recipient
|
|
65
|
+
approvalThreshold: 50_000_000, // flag above 0.5 ZEC
|
|
80
66
|
});
|
|
67
|
+
console.log("Policy:", policy.policyId);
|
|
81
68
|
|
|
82
|
-
//
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
memo: "payment for API access",
|
|
69
|
+
// 3. Check if a spend is allowed
|
|
70
|
+
const check = await checkPolicy(config, policy.policyId, {
|
|
71
|
+
amount: 10_000_000,
|
|
72
|
+
recipient: "t1SomeAddress...",
|
|
87
73
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
74
|
+
console.log("Allowed:", check.allowed);
|
|
75
|
+
|
|
76
|
+
// 4. Spend from the transparent address
|
|
77
|
+
const spend = await spendTransparent(config, {
|
|
78
|
+
walletId: wallet.id,
|
|
79
|
+
encryptionSeed: wallet.encryptionSeed,
|
|
80
|
+
recipient: "t1RecipientAddr...",
|
|
81
|
+
amount: 10_000_000, // 0.1 ZEC
|
|
82
|
+
zebraRpcUrl: "http://localhost:8232",
|
|
83
|
+
});
|
|
84
|
+
console.log("Txid:", spend.txid);
|
|
92
85
|
```
|
|
93
86
|
|
|
94
|
-
##
|
|
87
|
+
## Architecture
|
|
95
88
|
|
|
96
89
|
```
|
|
97
|
-
Operator (
|
|
90
|
+
Operator (device / HSM)
|
|
98
91
|
|
|
|
99
|
-
| user key share
|
|
92
|
+
| user key share (encryption seed from DKG)
|
|
100
93
|
|
|
|
101
|
-
Ika MPC Network (2PC-MPC on Sui
|
|
94
|
+
Ika MPC Network (2PC-MPC on Sui)
|
|
102
95
|
|
|
|
103
96
|
| network key share (distributed across nodes)
|
|
104
97
|
|
|
|
105
|
-
+--
|
|
106
|
-
| max per tx, daily cap,
|
|
107
|
-
| the agent CANNOT modify its own limits
|
|
98
|
+
+-- Spend Policy (Sui Move contract)
|
|
99
|
+
| max per tx, daily cap, recipient whitelist, freeze
|
|
108
100
|
|
|
|
109
|
-
+-- Sign
|
|
110
|
-
+-- Sign
|
|
111
|
-
+-- Sign EVM
|
|
101
|
+
+-- Sign ZEC transparent (secp256k1 / ECDSA / DoubleSHA256)
|
|
102
|
+
+-- Sign BTC (secp256k1 / ECDSA / DoubleSHA256)
|
|
103
|
+
+-- Sign ETH/EVM (secp256k1 / ECDSA / KECCAK256)
|
|
104
|
+
|
|
|
105
|
+
TX Builder (Zcash v5, ZIP 244)
|
|
106
|
+
+-- UTXO fetch from Zebra
|
|
107
|
+
+-- Sighash computation
|
|
108
|
+
+-- Signature attachment
|
|
109
|
+
+-- Broadcast + attestation
|
|
112
110
|
|
|
|
113
111
|
ZAP1 Attestation (Zcash mainnet)
|
|
114
|
-
+-- every spend recorded
|
|
115
|
-
+--
|
|
116
|
-
+--
|
|
117
|
-
+-- full audit trail, verifiable by anyone
|
|
112
|
+
+-- every spend recorded in Merkle tree
|
|
113
|
+
+-- anchored to Zcash blockchain
|
|
114
|
+
+-- verified on 5 EVM/L1 chains
|
|
118
115
|
```
|
|
119
116
|
|
|
120
|
-
##
|
|
117
|
+
## Sign flow
|
|
118
|
+
|
|
119
|
+
1. **Presign** - pre-compute MPC ephemeral key share (Sui TX 1, poll for completion)
|
|
120
|
+
2. **Sign** - approve message + request signature (Sui TX 2, poll for completion)
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
- `getHistory()` and `checkCompliance()` against live ZAP1 API
|
|
124
|
-
- All Ika SDK primitives re-exported and typed
|
|
125
|
-
- Chain parameter configs for all signing modes
|
|
126
|
-
- DKG test script proving the full round-trip
|
|
122
|
+
Both transactions on Sui. The user partial is computed locally via WASM. Neither party sees the full private key.
|
|
127
123
|
|
|
128
|
-
##
|
|
124
|
+
## Spend flow (transparent)
|
|
129
125
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
1. Fetch UTXOs from Zebra RPC (`getaddressutxos`)
|
|
127
|
+
2. Build unsigned v5 TX with ZIP 244 sighash per input
|
|
128
|
+
3. Sign each sighash through Ika MPC (presign + sign)
|
|
129
|
+
4. Attach DER signatures to scriptSig fields
|
|
130
|
+
5. Broadcast via `sendrawtransaction`
|
|
131
|
+
6. Attest spend to ZAP1
|
|
135
132
|
|
|
136
|
-
##
|
|
133
|
+
## Policy enforcement
|
|
137
134
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
135
|
+
The Sui Move contract (`zap1_policy::policy`) stores:
|
|
136
|
+
- Per-transaction limit (zatoshis)
|
|
137
|
+
- 24-hour rolling daily cap
|
|
138
|
+
- Allowed recipient whitelist (empty = any)
|
|
139
|
+
- Emergency freeze toggle
|
|
140
|
+
|
|
141
|
+
Policy is checked before every MPC sign request. The contract owns the approval gate - you can't bypass it from the client.
|
|
142
|
+
|
|
143
|
+
Deploy: `sui client publish --path move/` then set `POLICY_PACKAGE_ID`.
|
|
144
|
+
|
|
145
|
+
## On-chain proof
|
|
146
|
+
|
|
147
|
+
secp256k1 dWallet on Ika testnet:
|
|
148
|
+
|
|
149
|
+
- dWalletId: `0xd9055400c88aeae675413b78143aa54e25eca7061ab659f54a42167cbfdd7aec`
|
|
150
|
+
- TX: [`CYrS5X1S3itHUtux4qS35AJz5AAyUaJYeWZuqm1CcX2L`](https://testnet.suivision.xyz/txblock/CYrS5X1S3itHUtux4qS35AJz5AAyUaJYeWZuqm1CcX2L)
|
|
151
|
+
- Compressed pubkey: `03ba9e85a85674df494520c2e80b804656fac54fe68668266f33fee9b03ad4b069`
|
|
152
|
+
- Derived ZEC t-addr: `t1Rqh1TKqXsSiaV4wrSDandEPccucpHEudn`
|
|
153
|
+
|
|
154
|
+
Attestation anchors verified on Arbitrum, Base, Hyperliquid, Solana, NEAR testnet.
|
|
155
|
+
|
|
156
|
+
## Environment variables
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
SUI_PRIVATE_KEY - Sui keypair for signing transactions
|
|
160
|
+
POLICY_PACKAGE_ID - Published Move package address (after sui client publish)
|
|
161
|
+
ZAP1_API_URL - ZAP1 attestation API (default: https://pay.frontiercompute.io)
|
|
162
|
+
ZAP1_API_KEY - API key for attestation
|
|
163
|
+
ZEBRA_RPC_URL - Zebra JSON-RPC endpoint for UTXO queries and broadcast
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Test scripts
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Create a new dWallet
|
|
170
|
+
SUI_PRIVATE_KEY=suiprivkey1... node dist/test-dkg.js
|
|
171
|
+
|
|
172
|
+
# Sign a test message through MPC
|
|
173
|
+
SUI_PRIVATE_KEY=... DWALLET_ID=0x... ENC_SEED=... node dist/test-sign.js
|
|
174
|
+
|
|
175
|
+
# End-to-end: DKG + presign + sign
|
|
176
|
+
SUI_PRIVATE_KEY=... node dist/test-e2e.js
|
|
177
|
+
```
|
|
144
178
|
|
|
145
179
|
## Stack
|
|
146
180
|
|
|
147
181
|
- [Ika](https://ika.xyz) - 2PC-MPC threshold signing on Sui
|
|
148
182
|
- [ZAP1](https://pay.frontiercompute.io) - on-chain attestation protocol
|
|
149
183
|
- [Zebra](https://github.com/ZcashFoundation/zebra) - Zcash node
|
|
150
|
-
- [
|
|
184
|
+
- [Sui Move](https://docs.sui.io/concepts/sui-move-concepts) - policy enforcement
|
|
151
185
|
|
|
152
186
|
## License
|
|
153
187
|
|
package/dist/hybrid.d.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid custody model for Zcash agents.
|
|
3
|
+
*
|
|
4
|
+
* Two custody paths, one interface:
|
|
5
|
+
*
|
|
6
|
+
* TRANSPARENT (secp256k1 via Ika MPC):
|
|
7
|
+
* - t-addr transactions signed through 2PC-MPC
|
|
8
|
+
* - Neither party holds the full key
|
|
9
|
+
* - Policy enforced by Sui Move contract
|
|
10
|
+
* - Same key signs BTC and ETH
|
|
11
|
+
*
|
|
12
|
+
* SHIELDED (RedPallas via local Orchard wallet):
|
|
13
|
+
* - Orchard transactions signed locally (direct key)
|
|
14
|
+
* - Ika cannot sign RedPallas (Pallas curve not supported)
|
|
15
|
+
* - Privacy from Zcash protocol, not from MPC
|
|
16
|
+
* - Every shielded spend attested to ZAP1
|
|
17
|
+
*
|
|
18
|
+
* The gap:
|
|
19
|
+
* Orchard uses RedPallas on the Pallas curve. Ika supports secp256k1,
|
|
20
|
+
* Ed25519, secp256r1, and Ristretto. None of these are Pallas.
|
|
21
|
+
* Until an MPC network adds Pallas curve support, shielded custody
|
|
22
|
+
* requires holding keys directly.
|
|
23
|
+
*
|
|
24
|
+
* The bridge:
|
|
25
|
+
* shield() - move ZEC from t-addr (MPC) to shielded pool (local wallet)
|
|
26
|
+
* unshield() - move ZEC from shielded back to t-addr (for MPC custody)
|
|
27
|
+
* Both operations attested on-chain via ZAP1.
|
|
28
|
+
*/
|
|
29
|
+
import type { ZcashIkaConfig, DWalletHandle, SignResult } from "./index.js";
|
|
30
|
+
export type CustodyMode = "mpc" | "local";
|
|
31
|
+
export interface HybridWallet {
|
|
32
|
+
/** MPC-custody transparent wallet (secp256k1 dWallet on Ika) */
|
|
33
|
+
transparent: {
|
|
34
|
+
mode: "mpc";
|
|
35
|
+
walletId: string;
|
|
36
|
+
publicKey: Uint8Array;
|
|
37
|
+
tAddress: string;
|
|
38
|
+
encryptionSeed: string;
|
|
39
|
+
};
|
|
40
|
+
/** Local-custody shielded wallet (Orchard keys held directly) */
|
|
41
|
+
shielded: {
|
|
42
|
+
mode: "local";
|
|
43
|
+
/** Unified address (starts with u1) */
|
|
44
|
+
uAddress: string;
|
|
45
|
+
/** Whether the local wallet is initialized */
|
|
46
|
+
initialized: boolean;
|
|
47
|
+
};
|
|
48
|
+
/** ZAP1 attestation endpoint */
|
|
49
|
+
zap1ApiUrl: string;
|
|
50
|
+
}
|
|
51
|
+
export interface ShieldRequest {
|
|
52
|
+
/** Amount in zatoshis to move from t-addr to shielded pool */
|
|
53
|
+
amount: number;
|
|
54
|
+
/** Optional memo for the shielding transaction */
|
|
55
|
+
memo?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface UnshieldRequest {
|
|
58
|
+
/** Amount in zatoshis to move from shielded back to t-addr */
|
|
59
|
+
amount: number;
|
|
60
|
+
/** Optional memo */
|
|
61
|
+
memo?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface ShieldResult {
|
|
64
|
+
/** Zcash transaction ID */
|
|
65
|
+
txid: string;
|
|
66
|
+
/** Direction */
|
|
67
|
+
direction: "shield" | "unshield";
|
|
68
|
+
/** Amount moved */
|
|
69
|
+
amount: number;
|
|
70
|
+
/** ZAP1 attestation leaf hash */
|
|
71
|
+
leafHash?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create a hybrid wallet - MPC for transparent, local for shielded.
|
|
75
|
+
*
|
|
76
|
+
* The transparent side is an Ika dWallet (already created via createWallet).
|
|
77
|
+
* The shielded side connects to the local Zebra/Zashi wallet.
|
|
78
|
+
*/
|
|
79
|
+
export declare function createHybridWallet(transparentWallet: DWalletHandle, shieldedAddress: string, zap1ApiUrl: string): HybridWallet;
|
|
80
|
+
/**
|
|
81
|
+
* Sign a transparent transaction through MPC.
|
|
82
|
+
* Delegates to the Ika sign() function.
|
|
83
|
+
*/
|
|
84
|
+
export declare function signTransparent(config: ZcashIkaConfig, wallet: HybridWallet, sighash: Uint8Array): Promise<SignResult>;
|
|
85
|
+
/**
|
|
86
|
+
* Shield ZEC - move from MPC-custody t-addr to local shielded pool.
|
|
87
|
+
*
|
|
88
|
+
* Flow:
|
|
89
|
+
* 1. Build transparent tx: t-addr -> shielded address
|
|
90
|
+
* 2. Compute sighash (DoubleSHA256)
|
|
91
|
+
* 3. Sign via Ika MPC (secp256k1)
|
|
92
|
+
* 4. Broadcast via Zebra
|
|
93
|
+
* 5. Attest via ZAP1 as AGENT_ACTION
|
|
94
|
+
*
|
|
95
|
+
* After shielding, the ZEC is in the local Orchard wallet.
|
|
96
|
+
* Private from that point forward.
|
|
97
|
+
*/
|
|
98
|
+
export declare function shield(config: ZcashIkaConfig, wallet: HybridWallet, request: ShieldRequest): Promise<ShieldResult>;
|
|
99
|
+
/**
|
|
100
|
+
* Unshield ZEC - move from local shielded pool back to MPC-custody t-addr.
|
|
101
|
+
*
|
|
102
|
+
* Flow:
|
|
103
|
+
* 1. Build Orchard tx: shielded -> t-addr (signed locally, RedPallas)
|
|
104
|
+
* 2. Broadcast via Zebra
|
|
105
|
+
* 3. Attest via ZAP1 as AGENT_ACTION
|
|
106
|
+
*
|
|
107
|
+
* After unshielding, the ZEC is back under MPC custody.
|
|
108
|
+
* The MPC can then send it to BTC, ETH, or another t-addr.
|
|
109
|
+
*/
|
|
110
|
+
export declare function unshield(config: ZcashIkaConfig, wallet: HybridWallet, request: UnshieldRequest): Promise<ShieldResult>;
|
|
111
|
+
/**
|
|
112
|
+
* Attest a custody operation to ZAP1.
|
|
113
|
+
*/
|
|
114
|
+
export declare function attestCustodyOp(zap1ApiUrl: string, zap1ApiKey: string, op: {
|
|
115
|
+
direction: "shield" | "unshield";
|
|
116
|
+
txid: string;
|
|
117
|
+
amount: number;
|
|
118
|
+
walletId: string;
|
|
119
|
+
}): Promise<string | null>;
|
package/dist/hybrid.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid custody model for Zcash agents.
|
|
3
|
+
*
|
|
4
|
+
* Two custody paths, one interface:
|
|
5
|
+
*
|
|
6
|
+
* TRANSPARENT (secp256k1 via Ika MPC):
|
|
7
|
+
* - t-addr transactions signed through 2PC-MPC
|
|
8
|
+
* - Neither party holds the full key
|
|
9
|
+
* - Policy enforced by Sui Move contract
|
|
10
|
+
* - Same key signs BTC and ETH
|
|
11
|
+
*
|
|
12
|
+
* SHIELDED (RedPallas via local Orchard wallet):
|
|
13
|
+
* - Orchard transactions signed locally (direct key)
|
|
14
|
+
* - Ika cannot sign RedPallas (Pallas curve not supported)
|
|
15
|
+
* - Privacy from Zcash protocol, not from MPC
|
|
16
|
+
* - Every shielded spend attested to ZAP1
|
|
17
|
+
*
|
|
18
|
+
* The gap:
|
|
19
|
+
* Orchard uses RedPallas on the Pallas curve. Ika supports secp256k1,
|
|
20
|
+
* Ed25519, secp256r1, and Ristretto. None of these are Pallas.
|
|
21
|
+
* Until an MPC network adds Pallas curve support, shielded custody
|
|
22
|
+
* requires holding keys directly.
|
|
23
|
+
*
|
|
24
|
+
* The bridge:
|
|
25
|
+
* shield() - move ZEC from t-addr (MPC) to shielded pool (local wallet)
|
|
26
|
+
* unshield() - move ZEC from shielded back to t-addr (for MPC custody)
|
|
27
|
+
* Both operations attested on-chain via ZAP1.
|
|
28
|
+
*/
|
|
29
|
+
import { sign } from "./index.js";
|
|
30
|
+
/**
|
|
31
|
+
* Create a hybrid wallet - MPC for transparent, local for shielded.
|
|
32
|
+
*
|
|
33
|
+
* The transparent side is an Ika dWallet (already created via createWallet).
|
|
34
|
+
* The shielded side connects to the local Zebra/Zashi wallet.
|
|
35
|
+
*/
|
|
36
|
+
export function createHybridWallet(transparentWallet, shieldedAddress, zap1ApiUrl) {
|
|
37
|
+
return {
|
|
38
|
+
transparent: {
|
|
39
|
+
mode: "mpc",
|
|
40
|
+
walletId: transparentWallet.id,
|
|
41
|
+
publicKey: transparentWallet.publicKey,
|
|
42
|
+
tAddress: transparentWallet.address,
|
|
43
|
+
encryptionSeed: transparentWallet.encryptionSeed,
|
|
44
|
+
},
|
|
45
|
+
shielded: {
|
|
46
|
+
mode: "local",
|
|
47
|
+
uAddress: shieldedAddress,
|
|
48
|
+
initialized: shieldedAddress.startsWith("u1"),
|
|
49
|
+
},
|
|
50
|
+
zap1ApiUrl,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Sign a transparent transaction through MPC.
|
|
55
|
+
* Delegates to the Ika sign() function.
|
|
56
|
+
*/
|
|
57
|
+
export async function signTransparent(config, wallet, sighash) {
|
|
58
|
+
return sign(config, {
|
|
59
|
+
messageHash: sighash,
|
|
60
|
+
walletId: wallet.transparent.walletId,
|
|
61
|
+
chain: "zcash-transparent",
|
|
62
|
+
encryptionSeed: wallet.transparent.encryptionSeed,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Shield ZEC - move from MPC-custody t-addr to local shielded pool.
|
|
67
|
+
*
|
|
68
|
+
* Flow:
|
|
69
|
+
* 1. Build transparent tx: t-addr -> shielded address
|
|
70
|
+
* 2. Compute sighash (DoubleSHA256)
|
|
71
|
+
* 3. Sign via Ika MPC (secp256k1)
|
|
72
|
+
* 4. Broadcast via Zebra
|
|
73
|
+
* 5. Attest via ZAP1 as AGENT_ACTION
|
|
74
|
+
*
|
|
75
|
+
* After shielding, the ZEC is in the local Orchard wallet.
|
|
76
|
+
* Private from that point forward.
|
|
77
|
+
*/
|
|
78
|
+
export async function shield(config, wallet, request) {
|
|
79
|
+
if (!wallet.shielded.initialized) {
|
|
80
|
+
throw new Error("Shielded wallet not initialized. Provide a valid u1 address.");
|
|
81
|
+
}
|
|
82
|
+
if (!config.zebraRpcUrl) {
|
|
83
|
+
throw new Error("shield() requires zebraRpcUrl for building and broadcasting the tx.");
|
|
84
|
+
}
|
|
85
|
+
// The full pipeline:
|
|
86
|
+
// 1. z_listunspent on the t-addr to find UTXOs
|
|
87
|
+
// 2. Build raw tx spending to the shielded address
|
|
88
|
+
// 3. Extract sighash
|
|
89
|
+
// 4. sign() via Ika MPC
|
|
90
|
+
// 5. Attach signature
|
|
91
|
+
// 6. sendrawtransaction
|
|
92
|
+
// 7. Attest to ZAP1
|
|
93
|
+
throw new Error("shield() requires Zcash transparent tx builder (zcash-primitives or librustzcash). " +
|
|
94
|
+
"Use sign() with a pre-built sighash for now. " +
|
|
95
|
+
"The shielding tx is a standard t-addr -> Orchard spend.");
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Unshield ZEC - move from local shielded pool back to MPC-custody t-addr.
|
|
99
|
+
*
|
|
100
|
+
* Flow:
|
|
101
|
+
* 1. Build Orchard tx: shielded -> t-addr (signed locally, RedPallas)
|
|
102
|
+
* 2. Broadcast via Zebra
|
|
103
|
+
* 3. Attest via ZAP1 as AGENT_ACTION
|
|
104
|
+
*
|
|
105
|
+
* After unshielding, the ZEC is back under MPC custody.
|
|
106
|
+
* The MPC can then send it to BTC, ETH, or another t-addr.
|
|
107
|
+
*/
|
|
108
|
+
export async function unshield(config, wallet, request) {
|
|
109
|
+
if (!config.zebraRpcUrl) {
|
|
110
|
+
throw new Error("unshield() requires zebraRpcUrl.");
|
|
111
|
+
}
|
|
112
|
+
// Unshielding is done entirely locally - no MPC involved.
|
|
113
|
+
// The Orchard wallet holds keys directly and signs with RedPallas.
|
|
114
|
+
// z_sendmany from the shielded address to the t-addr.
|
|
115
|
+
throw new Error("unshield() requires connection to local Zebra wallet with Orchard spending keys. " +
|
|
116
|
+
"Use z_sendmany via Zebra RPC directly for now.");
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Attest a custody operation to ZAP1.
|
|
120
|
+
*/
|
|
121
|
+
export async function attestCustodyOp(zap1ApiUrl, zap1ApiKey, op) {
|
|
122
|
+
try {
|
|
123
|
+
const resp = await fetch(`${zap1ApiUrl}/attest`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: {
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
Authorization: `Bearer ${zap1ApiKey}`,
|
|
128
|
+
},
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
event_type: "AGENT_ACTION",
|
|
131
|
+
wallet_hash: op.walletId,
|
|
132
|
+
input_hash: op.txid,
|
|
133
|
+
memo: JSON.stringify({
|
|
134
|
+
action: op.direction,
|
|
135
|
+
amount: op.amount,
|
|
136
|
+
custody: op.direction === "shield" ? "mpc->local" : "local->mpc",
|
|
137
|
+
}),
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
if (!resp.ok)
|
|
141
|
+
return null;
|
|
142
|
+
const data = (await resp.json());
|
|
143
|
+
return data.leaf_hash || null;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|