Coconut_lib

The Coconut_lib is a development tool for mobile air gap Bitcoin wallets. It is written in Dart. Coconut Vault and Coconut Wallet were created using this library. Download from Appstore and Play Store.

And visit tutorial page for Self-custody we provided. (www.coconut.onl)

⚠ The Coconut_lib is still a project under development. Therefore, we are not responsible for any problems that may arise while using it. Please review it carefully and use it.

About

The Coconut_lib provides the base code for developing Bitcoin vaults and wallets based on Bitcoin airgap. Since coconut_lib is developed in Dart, it is specialized for developing applications for iPhone and Android by utilizing the Flutter. In particular, The Coconut_lib designed to develop air-gap-based vault and wallet apps separately by clearly distinguishing the vault area and wallet area. You can use the Coconut_lib to create your own air-gap based vault and wallet.

"Don't trust, verify and develop!"

Architecture

  • wallet: Provides a cryptography-based key management method. Create two apps instancing the Wallet and Vault classes. image
  • transaction: Provides code related to Bitcoin scripts and transactions. Also use PSBT(BIP-0174) to communicate vaults and wallets. image

For more development information, visit the coconut_lib docs.

Example

import 'package:coconut_lib/coconut_lib.dart';

import '../../test/mock_factory.dart';

void main() async {
  print("0. Set the Bitcoin Network");
  NetworkType.setNetworkType(NetworkType.regtest);

  print("1-1. Create a single signature vault");
  Seed seed = Seed.fromMnemonic(
      'thank split shrimp error own spirit slow glow act evidence globe slight');

  SingleSignatureVault singleSignatureVault =
      SingleSignatureVault.fromSeed(seed);
  print(
      ' - Master Fingerprint: ${singleSignatureVault.keyStore.masterFingerprint}');

  print("1-2. Create a 2-of-3 Multisignature vault");
  SingleSignatureVault insideVault1 = SingleSignatureVault.fromMnemonic(
      'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
      passphrase: 'ABC');

  SingleSignatureVault outsideVault1 = SingleSignatureVault.fromMnemonic(
      'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
      passphrase: 'DEF');

  SingleSignatureVault outsideVault2 = SingleSignatureVault.fromMnemonic(
      'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
      passphrase: 'GHI');

  //Generate P2WSH Keystore
  KeyStore insideKey1 =
      KeyStore.fromSeed(insideVault1.keyStore.seed, AddressType.p2wsh);
  KeyStore outsideKey1 = KeyStore.fromSignerBsms(
      outsideVault1.getSignerBsms(AddressType.p2wsh, "OutsideSigner1"));
  KeyStore outsideKey2 = KeyStore.fromSignerBsms(
      outsideVault2.getSignerBsms(AddressType.p2wsh, "OutsideSigner2"));

  MultisignatureVault multisignatureVault =
      MultisignatureVault.fromKeyStoreList(
          [insideKey1, outsideKey1, outsideKey2], 2);

  // Share Coordinator BSMS with Outside Signers
  MultisignatureVault outsideMultisignatureVault =
      MultisignatureVault.fromCoordinatorBsms(
          multisignatureVault.getCoordinatorBsms());

  // Find Seed in Outside Vault and bind it to KeyStore
  outsideMultisignatureVault.bindSeedToKeyStore(outsideVault1.keyStore.seed);

  print(
      ' - Master Fingerprint of Key Store [0]: ${multisignatureVault.keyStoreList[0].masterFingerprint}');
  print(
      ' - Master Fingerprint of Key Store [1]: ${multisignatureVault.keyStoreList[1].masterFingerprint}');
  print(
      ' - Master Fingerprint of Key Store [2]: ${multisignatureVault.keyStoreList[2].masterFingerprint}');

  print("2-1. Sync to the single signature wallet");
  // Repository.initialize('Coconut_Wallet');
  SingleSignatureWallet singleSignatureWallet =
      SingleSignatureWallet.fromDescriptor(singleSignatureVault.descriptor);
  print(
      ' - Extended Public Key: ${singleSignatureWallet.keyStore.extendedPublicKey.serialize()}');

  print("2-2. Sync to the multisignature wallet");
  MultisignatureWallet multisignatureWallet;

  Descriptor descriptor = Descriptor.parse(multisignatureVault.descriptor);
  if (descriptor.scriptType == 'wsh') {
    multisignatureWallet =
        MultisignatureWallet.fromDescriptor(multisignatureVault.descriptor);
    // } else if (descriptor.scriptType == 'wpkh') {
    //   watchOnlyWallet =
    //       SingleSignatureWallet.fromDescriptor(multisignatureVault.descriptor);
  } else {
    throw Exception('Unsupported Address Type');
  }
  print(
      ' - Extended Public Key of Key Store [0]: ${multisignatureWallet.keyStoreList[0].extendedPublicKey.serialize()}');
  print(
      ' - Extended Public Key of Key Store [1]: ${multisignatureWallet.keyStoreList[1].extendedPublicKey.serialize()}');
  print(
      ' - Extended Public Key of Key Store [2]: ${multisignatureWallet.keyStoreList[2].extendedPublicKey.serialize()}');

  print(
      "4. Send Bitcoin from the single signature wallet to the multisignature wallet");
  String receiverAddress = multisignatureWallet.getAddress(0);
  String changeAddress = singleSignatureWallet.getAddress(0, isChange: true);
  int sendingAmount = 1000;
  double feeRate = 3.0;
  List<Utxo> utxosForSingleSignatureWallet = [
    Utxo('5c5fa04bc94647ee339083d6fd381a3b1ac4de7d7bfa966788971d62072a1e66', 1,
        100000000, "m/84'/1'/0'/0/68")
  ];
  print(' - Generating unsigned PSBT');
  List<Utxo> utxoList = [
    Utxo('393a2d56f910019a6df975672989a449648f355b1fb7889fb831f0402c5550f3', 0,
        21000, "m/84'/1'/0'/0/0")
  ];
  Transaction unsignedTransaction = Transaction.forSinglePayment(utxoList,
      receiverAddress, "m/84'/1'/0'/1/0", 2000, 2, singleSignatureWallet);
  String unsignedPsbt =
      Psbt.fromTransaction(unsignedTransaction, singleSignatureWallet)
          .serialize();

  print(' - Add signature from vault');
  String signedPsbt = singleSignatureVault.addSignatureToPsbt(unsignedPsbt);
  Psbt walletReceivedPsbt = Psbt.parse(signedPsbt);
  Transaction signedTransaction = walletReceivedPsbt
      .getSignedTransaction(singleSignatureWallet.addressType);
  print(' - Final Transaction : ${signedTransaction.serialize()}');
}

Tests

Generate Mock Classes

dart pub run build_runner build

Unit Test

dart test -t unit

E2E Test

dart test -t e2e

Coverage

The following tools are required to generate test coverage (for MacOS):

dart pub global activate coverage

brew install lcov

To generate test coverage, run the following command:

sh ./generate_unit_coverage.sh

Bip Support List

  • BIP-11: M-of-N Standard Transactions
  • BIP-32: Hierarchical Deterministic Wallets
  • BIP-39: Mnemonic code for generating deterministic keys
  • BIP-44: Multi-Account Hierarchy for Deterministic Wallets
  • BIP-48: Multi-Script Hierarchy for Multi-Sig Wallets
  • BIP-67: Deterministic Multisig Key Sorting
  • BIP-84: Derivation scheme for P2WPKH based accounts
  • BIP-86: Key Derivation for Single Key P2TR Outputs
  • BIP-129: Bitcoin Secure Multisig Setup (BSMS)
  • BIP-142: Address Format for Segregated Witness
  • BIP-143: Transaction Signature Verification for Version 0 Witness Program
  • BIP-173: Base32 address format for native v0-16 witness outputs
  • BIP-174: Partially Signed Bitcoin Transaction Format
  • BIP-327: MuSig2 for BIP340-compatible Multi-Signatures
  • BIP-340: Schnorr Signatures for secp256k1
  • BIP-341: SegWit version 1 spending rules
  • BIP-370: PSBT Version 2
  • BIP-371: Taproot Fields for PSBT
  • BIP-380: Output Script Descriptors General Operation
  • BIP-381: Non-Segwit Output Script Descriptors
  • BIP-382: Segwit Output Script Descriptors
  • BIP-383: Multisig Output Script Descriptors

Contribution

Reference CONTRIBUTING

Bug report and Contact us

License

Reference LICENSE

Libraries

coconut_lib