Compare commits
No commits in common. "de7ef423dd681d8c29083dc2bea7bb8913b440ef" and "41d0d3d8668d01550677ab96f3877a08f5ed795c" have entirely different histories.
de7ef423dd
...
41d0d3d866
29 changed files with 0 additions and 667 deletions
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
|
|
@ -74,10 +74,6 @@ jobs:
|
||||||
echo "=== Cargo.lock entries for key crates ===" > ci-diagnostics/cargo-lock-entries.txt
|
echo "=== Cargo.lock entries for key crates ===" > ci-diagnostics/cargo-lock-entries.txt
|
||||||
grep -nE 'bdk|bip39|bip32|bitcoin|rusqlite|hidapi|aes-gcm|rusqlite' Cargo.lock >> ci-diagnostics/cargo-lock-entries.txt || true
|
grep -nE 'bdk|bip39|bip32|bitcoin|rusqlite|hidapi|aes-gcm|rusqlite' Cargo.lock >> ci-diagnostics/cargo-lock-entries.txt || true
|
||||||
|
|
||||||
- name: Run UniFFI codegen (if available)
|
|
||||||
run: |
|
|
||||||
./scripts/uniffi_codegen.sh || true
|
|
||||||
|
|
||||||
- name: Debug: cargo metadata & dependency tree
|
- name: Debug: cargo metadata & dependency tree
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ci-diagnostics
|
mkdir -p ci-diagnostics
|
||||||
|
|
@ -128,17 +124,3 @@ jobs:
|
||||||
npm ci || true
|
npm ci || true
|
||||||
npm test
|
npm test
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Run mobile unit tests (React Native)
|
|
||||||
working-directory: ./mobile
|
|
||||||
run: |
|
|
||||||
npm ci || true
|
|
||||||
npm test
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Run Electron smoke tests (desktop)
|
|
||||||
working-directory: ./electron
|
|
||||||
run: |
|
|
||||||
npm ci || true
|
|
||||||
npm test
|
|
||||||
shell: bash
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
# CrypteCipher
|
# CrypteCipher
|
||||||
|
|
||||||
> Status: core library, Node bindings, CLI, CI, mobile (React Native scaffold), and desktop (Electron scaffold) are in-progress. Mobile uses a JS mock bridge; UniFFI scaffold is present for on-device bindings.
|
|
||||||
|
|
||||||
# CrypteCipher
|
|
||||||
|
|
||||||
**PR:** Initial scaffold and features added for review.
|
**PR:** Initial scaffold and features added for review.
|
||||||
|
|
||||||
Secure privacy-first multi-currency wallet (scaffold)
|
Secure privacy-first multi-currency wallet (scaffold)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
// build.rs for UniFFI codegen (optional, no-op if uniffi-bindgen not present)
|
|
||||||
fn main() {
|
|
||||||
println!("cargo:rerun-if-changed=uniffi/cryptec.udl");
|
|
||||||
// Optionally, invoke uniffi-bindgen here for local dev builds
|
|
||||||
}
|
|
||||||
|
|
@ -30,9 +30,6 @@ pub use swap_manager::SwapOffer;
|
||||||
pub use swap_sim::SwapSimulation;
|
pub use swap_sim::SwapSimulation;
|
||||||
pub use wallet_manager::{register_decrypted_db, close_decrypted_db, register_inmemory_db, close_inmemory_db, close_all, list_handles};
|
pub use wallet_manager::{register_decrypted_db, close_decrypted_db, register_inmemory_db, close_inmemory_db, close_all, list_handles};
|
||||||
|
|
||||||
mod uniffi;
|
|
||||||
pub use uniffi::{generate_mnemonic, first_receive_address, create_psbt, sign_psbt};
|
|
||||||
|
|
||||||
/// WalletCore: minimal demonstrative API
|
/// WalletCore: minimal demonstrative API
|
||||||
pub struct WalletCore {
|
pub struct WalletCore {
|
||||||
// In a real implementation, secrets should be stored in a secure enclave or encrypted storage
|
// In a real implementation, secrets should be stored in a secure enclave or encrypted storage
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
use crate::bip39::Mnemonic as LocalMnemonic;
|
|
||||||
use crate::wallet_bdk::{BdkWallet, PersistentBdkWallet};
|
|
||||||
use bitcoin::Network;
|
|
||||||
|
|
||||||
/// UniFFI-compatible thin bridge functions consumed by mobile/native bindings.
|
|
||||||
/// These mirror the `cryptec.udl` UDL surface and delegate to existing core logic.
|
|
||||||
|
|
||||||
/// Generate a BIP39 mnemonic (wordlist phrase)
|
|
||||||
pub fn generate_mnemonic(strength: u32) -> String {
|
|
||||||
// Accept strengths like 128, 160, 192, 224, 256
|
|
||||||
let s = match strength {
|
|
||||||
128 | 160 | 192 | 224 | 256 => strength as usize,
|
|
||||||
_ => 128,
|
|
||||||
};
|
|
||||||
let m = LocalMnemonic::generate(s);
|
|
||||||
m.phrase
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the first (external 0) receive address for the given mnemonic (Testnet)
|
|
||||||
pub fn first_receive_address(mnemonic: &str) -> String {
|
|
||||||
// Reuse the BDK wallet helper which already derives descriptors/addresses
|
|
||||||
let w = match BdkWallet::new_from_mnemonic(mnemonic, "", Network::Testnet) {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(_) => return String::new(),
|
|
||||||
};
|
|
||||||
match w.get_new_address() {
|
|
||||||
Ok(a) => a,
|
|
||||||
Err(_) => String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a PSBT (base64) using the mnemonic (Testnet)
|
|
||||||
pub fn create_psbt(mnemonic: &str, to_address: &str, satoshis: u64) -> String {
|
|
||||||
let w = BdkWallet::new_from_mnemonic(mnemonic, "", Network::Testnet)
|
|
||||||
.expect("create wallet");
|
|
||||||
w.create_psbt(to_address, satoshis).expect("create psbt")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sign a PSBT (base64) with the wallet's internal keys (Testnet)
|
|
||||||
pub fn sign_psbt(mnemonic: &str, psbt_b64: &str) -> String {
|
|
||||||
let w = BdkWallet::new_from_mnemonic(mnemonic, "", Network::Testnet)
|
|
||||||
.expect("create wallet");
|
|
||||||
w.sign_psbt_base64(psbt_b64).expect("sign psbt")
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
use crate::uniffi::{generate_mnemonic, first_receive_address, create_psbt, sign_psbt};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn uniffi_generate_and_address() {
|
|
||||||
let m = generate_mnemonic(128);
|
|
||||||
assert!(!m.is_empty());
|
|
||||||
let addr = first_receive_address(&m);
|
|
||||||
assert!(!addr.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn uniffi_psbt_roundtrip_mock() {
|
|
||||||
let m = generate_mnemonic(128);
|
|
||||||
let addr = first_receive_address(&m);
|
|
||||||
assert!(!addr.is_empty());
|
|
||||||
let psbt = create_psbt(&m, &addr, 1000);
|
|
||||||
assert!(!psbt.is_empty());
|
|
||||||
let signed = sign_psbt(&m, &psbt);
|
|
||||||
assert!(!signed.is_empty());
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
UniFFI scaffold for CrypteCipher core
|
|
||||||
|
|
||||||
This folder contains a UDL interface describing the minimal set of APIs the mobile app will consume on-device.
|
|
||||||
|
|
||||||
How to generate bindings (developer machine):
|
|
||||||
|
|
||||||
1. Install `uniffi-bindgen` (Rust/Cargo or prebuilt binary).
|
|
||||||
2. From `core/` run:
|
|
||||||
```bash
|
|
||||||
uniffi-bindgen generate uniffi/cryptec.udl -l objc -o bindings/ios
|
|
||||||
uniffi-bindgen generate uniffi/cryptec.udl -l java -o bindings/android
|
|
||||||
```
|
|
||||||
3. Implement platform-specific glue in the generated wrappers and add React Native native modules that call them.
|
|
||||||
|
|
||||||
Note: CI currently ships a JS fallback for mobile; UniFFI integration will be enabled in CI once native toolchains are added.
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
// UniFFI-generated placeholder for Android (stub).
|
|
||||||
// The real generated class will provide JNI hooks to the Rust library.
|
|
||||||
package org.cryptec.uniffi.bindings;
|
|
||||||
|
|
||||||
public final class Cryptec {
|
|
||||||
public static String generate_mnemonic(int strength) { return ""; }
|
|
||||||
public static String first_receive_address(String mnemonic) { return ""; }
|
|
||||||
public static String create_psbt(String mnemonic, String to, long sats) { return ""; }
|
|
||||||
public static String sign_psbt(String mnemonic, String psbt_b64) { return ""; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
// UniFFI placeholder header (iOS) - generated bindings will provide concrete implementations
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface Cryptec : NSObject
|
|
||||||
|
|
||||||
+ (NSString*)generate_mnemonic:(uint32_t)strength;
|
|
||||||
+ (NSString*)first_receive_address:(NSString*)mnemonic;
|
|
||||||
+ (NSString*)create_psbt:(NSString*)mnemonic to:(NSString*)to sats:(uint64_t)sats;
|
|
||||||
+ (NSString*)sign_psbt:(NSString*)mnemonic psbt:(NSString*)psbt_b64;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
namespace cryptec {
|
|
||||||
|
|
||||||
string generate_mnemonic(in uint32 strength);
|
|
||||||
string first_receive_address(in string mnemonic);
|
|
||||||
string create_psbt(in string mnemonic, in string to_address, in uint64 satoshis);
|
|
||||||
string sign_psbt(in string mnemonic, in string psbt_b64);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
const { app, BrowserWindow } = require('electron');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
function createWindow() {
|
|
||||||
const win = new BrowserWindow({
|
|
||||||
width: 900,
|
|
||||||
height: 700,
|
|
||||||
webPreferences: {
|
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
|
||||||
contextIsolation: true,
|
|
||||||
nodeIntegration: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
win.loadFile(path.join(__dirname, 'renderer', 'index.html'));
|
|
||||||
}
|
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
|
||||||
createWindow();
|
|
||||||
app.on('activate', function () {
|
|
||||||
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.on('window-all-closed', function () {
|
|
||||||
if (process.platform !== 'darwin') app.quit();
|
|
||||||
});
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"name": "cryptec-electron",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"main": "main.js",
|
|
||||||
"scripts": {
|
|
||||||
"start": "electron .",
|
|
||||||
"test": "node ./test/electron-smoke.js"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"electron": "^25.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
const { contextBridge } = require('electron');
|
|
||||||
|
|
||||||
let nativeBindings = null;
|
|
||||||
try {
|
|
||||||
// Try to require the Node bindings package (napi). If not available, fall back to a JS shim.
|
|
||||||
nativeBindings = require(pathJoin(__dirname, '..', 'bindings'));
|
|
||||||
} catch (e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
function pathJoin(...parts) {
|
|
||||||
return parts.join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
const shim = {
|
|
||||||
generate_mnemonic: (strength = 128) => {
|
|
||||||
// simple in-process fallback
|
|
||||||
const crypto = require('crypto');
|
|
||||||
// use 128-bit entropy => 12 words via bip39 library if available
|
|
||||||
try {
|
|
||||||
const bip39 = require('bip39');
|
|
||||||
return bip39.generateMnemonic(strength);
|
|
||||||
} catch (e) {
|
|
||||||
return crypto.randomBytes(16).toString('hex');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
first_receive_address: (mnemonic) => {
|
|
||||||
try {
|
|
||||||
const bip39 = require('bip39');
|
|
||||||
const bitcoin = require('bitcoinjs-lib');
|
|
||||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
|
||||||
const root = bitcoin.bip32.fromSeed(seed, bitcoin.networks.testnet);
|
|
||||||
const child = root.derivePath("m/84'/1'/0'/0/0");
|
|
||||||
const { address } = bitcoin.payments.p2wpkh({ pubkey: child.publicKey, network: bitcoin.networks.testnet });
|
|
||||||
return address || '';
|
|
||||||
} catch (e) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
create_psbt: (_mnemonic, toAddress, satoshis) => {
|
|
||||||
return Buffer.from(JSON.stringify({ type: 'psbt-mock', to: toAddress, sats: satoshis })).toString('base64');
|
|
||||||
},
|
|
||||||
sign_psbt: (_mnemonic, psbtB64) => {
|
|
||||||
const decoded = Buffer.from(psbtB64, 'base64').toString('utf8');
|
|
||||||
return Buffer.from(JSON.stringify({ signed: true, original: decoded })).toString('base64');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const api = nativeBindings ? nativeBindings : shim;
|
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('cryptec', {
|
|
||||||
generateMnemonic: (strength) => api.generate_mnemonic ? api.generate_mnemonic(strength) : api.generate_mnemonic(strength),
|
|
||||||
firstReceiveAddress: (mnemonic) => api.first_receive_address ? api.first_receive_address(mnemonic) : api.first_receive_address(mnemonic),
|
|
||||||
createPsbt: (mnemonic, to, sats) => api.create_psbt ? api.create_psbt(mnemonic, to, sats) : api.create_psbt(mnemonic, to, sats),
|
|
||||||
signPsbt: (mnemonic, psbtB64) => api.sign_psbt ? api.sign_psbt(mnemonic, psbtB64) : api.sign_psbt(mnemonic, psbtB64)
|
|
||||||
});
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>CrypteCipher Desktop</title>
|
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' data:;" />
|
|
||||||
<style>
|
|
||||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial; margin: 0; padding: 20px; }
|
|
||||||
.container { max-width: 820px; margin: 0 auto; }
|
|
||||||
.addr { background: #f6f6f6; padding: 12px; border-radius: 6px; font-family: monospace; }
|
|
||||||
.section { margin-top: 18px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>CrypteCipher — Desktop (Electron)</h1>
|
|
||||||
<div>
|
|
||||||
<button id="new">Create / Generate mnemonic</button>
|
|
||||||
<button id="addr">Get first receive address</button>
|
|
||||||
</div>
|
|
||||||
<div class="section">
|
|
||||||
<h3>Mnemonic</h3>
|
|
||||||
<div id="mn" class="addr">(none)</div>
|
|
||||||
</div>
|
|
||||||
<div class="section">
|
|
||||||
<h3>Receive Address</h3>
|
|
||||||
<div id="addrbox" class="addr">(none)</div>
|
|
||||||
</div>
|
|
||||||
<div class="section">
|
|
||||||
<h3>Send (mock)</h3>
|
|
||||||
<input id="to" placeholder="to address" style="width:60%" />
|
|
||||||
<input id="sats" placeholder="satoshis" style="width:20%" />
|
|
||||||
<button id="psbt">Create PSBT</button>
|
|
||||||
</div>
|
|
||||||
<pre id="out"></pre>
|
|
||||||
</div>
|
|
||||||
<script src="./renderer.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
const $ = (id) => document.getElementById(id);
|
|
||||||
|
|
||||||
$('new').addEventListener('click', async () => {
|
|
||||||
const m = await window.cryptec.generateMnemonic(128);
|
|
||||||
$('mn').innerText = m;
|
|
||||||
});
|
|
||||||
|
|
||||||
$('addr').addEventListener('click', () => {
|
|
||||||
const mn = $('mn').innerText;
|
|
||||||
if (!mn) return alert('generate or enter mnemonic first');
|
|
||||||
const a = window.cryptec.firstReceiveAddress(mn);
|
|
||||||
$('addrbox').innerText = a;
|
|
||||||
});
|
|
||||||
|
|
||||||
$('psbt').addEventListener('click', async () => {
|
|
||||||
const mn = $('mn').innerText;
|
|
||||||
const to = $('to').value || 'tb1qexampleaddress';
|
|
||||||
const sats = parseInt($('sats').value || '1000', 10);
|
|
||||||
const psbt = await window.cryptec.createPsbt(mn, to, sats);
|
|
||||||
$('out').innerText = psbt;
|
|
||||||
});
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
const { execSync } = require('child_process');
|
|
||||||
const path = require('path');
|
|
||||||
try {
|
|
||||||
// smoke: ensure electron module can be required
|
|
||||||
const electron = require('electron');
|
|
||||||
console.log('electron available:', !!electron);
|
|
||||||
console.log('Electron smoke test passed');
|
|
||||||
process.exit(0);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Electron smoke test failed', e.message);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import nativeBridge from '../src/nativeBridge';
|
|
||||||
|
|
||||||
test('generate mnemonic and derive address', async () => {
|
|
||||||
const m = await nativeBridge.generateMnemonic(128);
|
|
||||||
expect(typeof m).toBe('string');
|
|
||||||
const addr = nativeBridge.firstReceiveAddressFromMnemonic(m);
|
|
||||||
expect(typeof addr).toBe('string');
|
|
||||||
expect(addr.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('create and sign psbt mock', async () => {
|
|
||||||
const mn = await nativeBridge.generateMnemonic(128);
|
|
||||||
const psbt = await nativeBridge.createPsbtMock(mn, 'tb1qexampleaddress', 5000);
|
|
||||||
expect(psbt).toBeTruthy();
|
|
||||||
const signed = await nativeBridge.signPsbtMock(mn, psbt);
|
|
||||||
expect(signed).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import { NativeModules } from 'react-native';
|
|
||||||
import nativeBridge from '../src/nativeBridge';
|
|
||||||
|
|
||||||
describe('native module integration (shim)', () => {
|
|
||||||
afterEach(() => {
|
|
||||||
jest.resetModules();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('JS mock used when NativeModules not available', async () => {
|
|
||||||
const mn = await nativeBridge.generateMnemonic(128);
|
|
||||||
expect(typeof mn).toBe('string');
|
|
||||||
const addr = nativeBridge.firstReceiveAddressFromMnemonic(mn);
|
|
||||||
expect(typeof addr).toBe('string');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('uses NativeModules when present (mocked)', async () => {
|
|
||||||
(NativeModules as any).Cryptec = {
|
|
||||||
generate_mnemonic: (s: number) => 'abandon abandon abandon ...',
|
|
||||||
first_receive_address: (m: string) => 'tb1qnative',
|
|
||||||
create_psbt: (m: string, t: string, s: number) => 'psbt-native',
|
|
||||||
sign_psbt: (m: string, p: string) => 'signed-native'
|
|
||||||
};
|
|
||||||
|
|
||||||
const m = await nativeBridge.generateMnemonic(128);
|
|
||||||
expect(m).toContain('abandon');
|
|
||||||
const addr = nativeBridge.firstReceiveAddressFromMnemonic('x');
|
|
||||||
expect(addr).toBe('tb1qnative');
|
|
||||||
|
|
||||||
const psbt = await nativeBridge.createPsbtMock('x', 'tb1qnative', 1000);
|
|
||||||
expect(psbt).toBe('psbt-native');
|
|
||||||
|
|
||||||
const signed = await nativeBridge.signPsbtMock('x', 'psbt-native');
|
|
||||||
expect(signed).toBe('signed-native');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package com.cryptec;
|
|
||||||
|
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
|
||||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
||||||
import com.facebook.react.bridge.ReactMethod;
|
|
||||||
import com.facebook.react.bridge.Promise;
|
|
||||||
|
|
||||||
public class CryptecModule extends ReactContextBaseJavaModule {
|
|
||||||
public CryptecModule(ReactApplicationContext reactContext) {
|
|
||||||
super(reactContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "Cryptec";
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void generate_mnemonic(int strength, Promise p) {
|
|
||||||
// Native implementation should call into UniFFI-generated JNI bridge.
|
|
||||||
// Fallback: return an error so JS fallback can be used in dev/CI.
|
|
||||||
p.resolve("");
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void first_receive_address(String mnemonic, Promise p) {
|
|
||||||
p.resolve("");
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void create_psbt(String mnemonic, String to, double sats, Promise p) {
|
|
||||||
p.resolve("");
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void sign_psbt(String mnemonic, String psbt_b64, Promise p) {
|
|
||||||
p.resolve("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package com.cryptec;
|
|
||||||
|
|
||||||
import com.facebook.react.ReactPackage;
|
|
||||||
import com.facebook.react.bridge.NativeModule;
|
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
|
||||||
import com.facebook.react.uimanager.ViewManager;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class CryptecPackage implements ReactPackage {
|
|
||||||
@Override
|
|
||||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
|
||||||
List<NativeModule> modules = new ArrayList<>();
|
|
||||||
modules.add(new CryptecModule(reactContext));
|
|
||||||
return modules;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
#import <React/RCTBridgeModule.h>
|
|
||||||
|
|
||||||
@interface RCT_EXTERN_MODULE(Cryptec, NSObject)
|
|
||||||
|
|
||||||
RCT_EXTERN_METHOD(generate_mnemonic:(NSInteger)strength resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
||||||
RCT_EXTERN_METHOD(first_receive_address:(NSString*)mnemonic resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
||||||
RCT_EXTERN_METHOD(create_psbt:(NSString*)mnemonic to:(NSString*)to sats:(nonnull NSNumber*)sats resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
||||||
RCT_EXTERN_METHOD(sign_psbt:(NSString*)mnemonic psbt:(NSString*)psbt resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
preset: 'react-native',
|
|
||||||
transform: {
|
|
||||||
'^.+\\.tsx?$': 'ts-jest'
|
|
||||||
},
|
|
||||||
testEnvironment: 'node',
|
|
||||||
setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect']
|
|
||||||
};
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
"name": "cryptec-mobile",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"main": "node_modules/expo/AppEntry.js",
|
|
||||||
"scripts": {
|
|
||||||
"start": "expo start",
|
|
||||||
"android": "expo run:android",
|
|
||||||
"ios": "expo run:ios",
|
|
||||||
"web": "expo start --web",
|
|
||||||
"test": "jest --config ./jest.config.js --runInBand",
|
|
||||||
"lint": "eslint . --ext .ts,.tsx",
|
|
||||||
"prebuild": "echo 'prebuild hook (placeholder for native module generation)'"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"expo": "~48.0.0",
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-native": "0.71.8",
|
|
||||||
"@react-navigation/native": "^6.1.6",
|
|
||||||
"@react-navigation/native-stack": "^6.9.12",
|
|
||||||
"bip39": "^3.0.4",
|
|
||||||
"bitcoinjs-lib": "^6.1.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@testing-library/react-native": "^11.5.0",
|
|
||||||
"@types/jest": "^29.5.2",
|
|
||||||
"@types/react": "18.0.28",
|
|
||||||
"@types/react-native": "0.70.14",
|
|
||||||
"jest": "^29.5.0",
|
|
||||||
"react-test-renderer": "18.2.0",
|
|
||||||
"ts-jest": "^29.1.0",
|
|
||||||
"typescript": "^4.9.5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
|
||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
|
||||||
import Onboarding from './screens/Onboarding';
|
|
||||||
import WalletHome from './screens/WalletHome';
|
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator();
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
return (
|
|
||||||
<NavigationContainer>
|
|
||||||
<Stack.Navigator initialRouteName="Onboarding">
|
|
||||||
<Stack.Screen name="Onboarding" component={Onboarding} options={{ title: 'Welcome' }} />
|
|
||||||
<Stack.Screen name="Wallet" component={WalletHome} options={{ title: 'Wallet' }} />
|
|
||||||
</Stack.Navigator>
|
|
||||||
</NavigationContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
import { NativeModules } from 'react-native';
|
|
||||||
import * as bip39 from 'bip39';
|
|
||||||
import * as bitcoin from 'bitcoinjs-lib';
|
|
||||||
|
|
||||||
const NETWORK = bitcoin.networks.testnet;
|
|
||||||
|
|
||||||
// If a native mobile binding (UniFFI) is installed, use it. Otherwise fall back to JS mock.
|
|
||||||
const NativeCryptec: any = (NativeModules && (NativeModules.Cryptec || NativeModules.CryptecModule)) || null;
|
|
||||||
|
|
||||||
async function generateMnemonicNative(strength = 128): Promise<string> {
|
|
||||||
if (NativeCryptec && NativeCryptec.generate_mnemonic) {
|
|
||||||
return await NativeCryptec.generate_mnemonic(strength);
|
|
||||||
}
|
|
||||||
return bip39.generateMnemonic(strength);
|
|
||||||
}
|
|
||||||
|
|
||||||
function firstReceiveAddressFromMnemonicNative(mnemonic: string): string {
|
|
||||||
if (NativeCryptec && NativeCryptec.first_receive_address) {
|
|
||||||
try {
|
|
||||||
return NativeCryptec.first_receive_address(mnemonic);
|
|
||||||
} catch (e) {
|
|
||||||
// fallback to JS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
|
||||||
const root = bitcoin.bip32.fromSeed(seed, NETWORK);
|
|
||||||
const child = root.derivePath("m/84'/1'/0'/0/0");
|
|
||||||
const { publicKey } = child;
|
|
||||||
const { address } = bitcoin.payments.p2wpkh({ pubkey: publicKey, network: NETWORK });
|
|
||||||
return address || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createPsbtNative(mnemonic: string, toAddress: string, satoshis: number): Promise<string> {
|
|
||||||
if (NativeCryptec && NativeCryptec.create_psbt) {
|
|
||||||
return await NativeCryptec.create_psbt(mnemonic, toAddress, satoshis);
|
|
||||||
}
|
|
||||||
const payload = { type: 'psbt-mock', to: toAddress, sats: satoshis };
|
|
||||||
return Buffer.from(JSON.stringify(payload)).toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function signPsbtNative(mnemonic: string, psbtB64: string): Promise<string> {
|
|
||||||
if (NativeCryptec && NativeCryptec.sign_psbt) {
|
|
||||||
return await NativeCryptec.sign_psbt(mnemonic, psbtB64);
|
|
||||||
}
|
|
||||||
const decoded = Buffer.from(psbtB64, 'base64').toString('utf8');
|
|
||||||
const payload = { signed: true, original: decoded };
|
|
||||||
return Buffer.from(JSON.stringify(payload)).toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
generateMnemonic: generateMnemonicNative,
|
|
||||||
firstReceiveAddressFromMnemonic: firstReceiveAddressFromMnemonicNative,
|
|
||||||
createPsbtMock: createPsbtNative,
|
|
||||||
signPsbtMock: signPsbtNative
|
|
||||||
};
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { View, Text, Button, StyleSheet, TextInput } from 'react-native';
|
|
||||||
import nativeBridge from '../nativeBridge';
|
|
||||||
|
|
||||||
export default function Onboarding({ navigation }: any) {
|
|
||||||
const [mnemonic, setMnemonic] = useState('');
|
|
||||||
|
|
||||||
const createNew = async () => {
|
|
||||||
const m = await nativeBridge.generateMnemonic(128);
|
|
||||||
setMnemonic(m);
|
|
||||||
navigation.navigate('Wallet', { mnemonic: m });
|
|
||||||
};
|
|
||||||
|
|
||||||
const importExisting = () => {
|
|
||||||
if (!mnemonic) return;
|
|
||||||
navigation.navigate('Wallet', { mnemonic });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.container}>
|
|
||||||
<Text style={styles.title}>CrypteCipher — Onboarding</Text>
|
|
||||||
<Button title="Create new wallet" onPress={createNew} />
|
|
||||||
<Text style={styles.or}>— or —</Text>
|
|
||||||
<TextInput
|
|
||||||
style={styles.input}
|
|
||||||
placeholder="paste mnemonic phrase to import"
|
|
||||||
value={mnemonic}
|
|
||||||
onChangeText={setMnemonic}
|
|
||||||
multiline
|
|
||||||
/>
|
|
||||||
<Button title="Import wallet" onPress={importExisting} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, padding: 20, justifyContent: 'center' },
|
|
||||||
title: { fontSize: 20, fontWeight: '600', marginBottom: 20 },
|
|
||||||
or: { textAlign: 'center', marginVertical: 12, color: '#666' },
|
|
||||||
input: { borderWidth: 1, borderColor: '#ddd', padding: 8, marginBottom: 12, borderRadius: 6 }
|
|
||||||
});
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { View, Text, Button, StyleSheet, TextInput, Alert } from 'react-native';
|
|
||||||
import nativeBridge from '../nativeBridge';
|
|
||||||
|
|
||||||
export default function WalletHome({ route }: any) {
|
|
||||||
const { mnemonic } = route.params || {};
|
|
||||||
const [address, setAddress] = useState('');
|
|
||||||
const [to, setTo] = useState('');
|
|
||||||
const [amount, setAmount] = useState('1000');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const addr = nativeBridge.firstReceiveAddressFromMnemonic(mnemonic);
|
|
||||||
setAddress(addr);
|
|
||||||
}, [mnemonic]);
|
|
||||||
|
|
||||||
const createPsbt = async () => {
|
|
||||||
const psbt = await nativeBridge.createPsbtMock(mnemonic, to, parseInt(amount || '0', 10));
|
|
||||||
Alert.alert('PSBT created (mock)', psbt);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.container}>
|
|
||||||
<Text style={styles.title}>Wallet</Text>
|
|
||||||
<Text style={styles.label}>Receive address (first):</Text>
|
|
||||||
<Text style={styles.addr}>{address}</Text>
|
|
||||||
|
|
||||||
<Text style={styles.section}>Send (mock)</Text>
|
|
||||||
<TextInput placeholder="to address" style={styles.input} value={to} onChangeText={setTo} />
|
|
||||||
<TextInput placeholder="satoshis" style={styles.input} value={amount} onChangeText={setAmount} keyboardType="numeric" />
|
|
||||||
<Button title="Create PSBT (mock)" onPress={createPsbt} />
|
|
||||||
|
|
||||||
<View style={{ height: 24 }} />
|
|
||||||
<Button title="Export mnemonic (copy)" onPress={() => Alert.alert('Mnemonic', mnemonic)} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: { flex: 1, padding: 20 },
|
|
||||||
title: { fontSize: 20, fontWeight: '600', marginBottom: 12 },
|
|
||||||
label: { fontWeight: '500', marginTop: 8 },
|
|
||||||
addr: { marginVertical: 8, color: '#333' },
|
|
||||||
section: { marginTop: 16, fontWeight: '600' },
|
|
||||||
input: { borderWidth: 1, borderColor: '#ddd', padding: 8, marginVertical: 8, borderRadius: 6 }
|
|
||||||
});
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es2019",
|
|
||||||
"module": "esnext",
|
|
||||||
"jsx": "react-native",
|
|
||||||
"strict": true,
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"noEmit": true
|
|
||||||
},
|
|
||||||
"exclude": ["node_modules", "babel.config.js", "metro.config.js", "jest.config.js"]
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
UDL=core/uniffi/cryptec.udl
|
|
||||||
OUT_ANDROID=core/uniffi/bindings/android
|
|
||||||
OUT_IOS=core/uniffi/bindings/ios
|
|
||||||
|
|
||||||
echo "[uniffi-codegen] UDL: $UDL"
|
|
||||||
|
|
||||||
if command -v uniffi-bindgen >/dev/null 2>&1; then
|
|
||||||
echo "[uniffi-codegen] running uniffi-bindgen (java, objc)"
|
|
||||||
uniffi-bindgen generate "$UDL" -l java -o "$OUT_ANDROID"
|
|
||||||
uniffi-bindgen generate "$UDL" -l objc -o "$OUT_IOS"
|
|
||||||
echo "[uniffi-codegen] generated bindings in $OUT_ANDROID and $OUT_IOS"
|
|
||||||
else
|
|
||||||
echo "[uniffi-codegen] uniffi-bindgen not found; skipping codegen (placeholders must exist)"
|
|
||||||
fi
|
|
||||||
Loading…
Add table
Reference in a new issue