Requires the mops package manager for Motoko.
mops add bitcoinBase58:
import Base58 "mo:bitcoin/Base58";
let encoded : Text = Base58.encode([0x00, 0x01, 0x02]);
assert Base58.decodeOpt(encoded) == ?[0x00, 0x01, 0x02];
Base58Check:
import Base58Check "mo:bitcoin/Base58Check";
let encoded : Text = Base58Check.encode([0x00, 0x01, 0x02]);
assert Base58Check.decode(encoded) == ?[0x00, 0x01, 0x02];
HMAC:
import Hmac "mo:bitcoin/Hmac";
let key : [Nat8] = [0x01, 0x02, 0x03];
// HMAC-SHA256
let hmacSha256 : Hmac.Hmac = Hmac.sha256(key);
hmacSha256.writeArray([0x04, 0x05]);
var result : Blob = hmacSha256.sum();
// HMAC-SHA512
let hmacSha512 : Hmac.Hmac = Hmac.sha512(key);
hmacSha512.writeArray([0x04, 0x05]);
result := hmacSha512.sum();
// HMAC with a custom hash function
let hmacCustom : Hmac.Hmac = Hmac.new(
key,
{
blockSize = 64;
create = func() : Hmac.Digest {
{
writeArray = func(_data : [Nat8]) { /* process input */ };
sum = func() : Blob = "" /* compute the hash */;
};
};
},
);
hmacCustom.writeArray([0x04, 0x05]);
result := hmacCustom.sum();
An Hmac is one-shot: after sum(), further calls trap. Create a new one for
the next HMAC.
RIPEMD160:
import Ripemd160 "mo:bitcoin/Ripemd160";
let digest = Ripemd160.Digest();
digest.write([0x01, 0x02]);
digest.write([0x03, 0x04]);
let result : [Nat8] = digest.sum();
// One-shot
assert Ripemd160.hash([0x01, 0x02, 0x03, 0x04]) == result;
EC:
import Jacobi "mo:bitcoin/ec/Jacobi";
import Affine "mo:bitcoin/ec/Affine";
import Curves "mo:bitcoin/ec/Curves";
// Get secp256k1 curve parameters.
let secp256k1 : Curves.Curve = Curves.secp256k1;
let Fp = secp256k1.Fp;
// Create affine point on the secp256k1 curve
let basePointAffine : Affine.Point = #point(Fp(secp256k1.gx), Fp(secp256k1.gy), secp256k1);
// Convert to Jacobi point
let basePointJacobi : Jacobi.Point = Jacobi.fromAffine(basePointAffine);
// Scalar multiplication
let mul1 = Jacobi.mul(basePointJacobi, 1234);
let mul2 = Jacobi.mulBase(1234, Curves.secp256k1);
assert Jacobi.isEqual(mul1, mul2);
BIP32:
import Bip32 "mo:bitcoin/Bip32";
let rootKey : ?Bip32.ExtendedPublicKey = Bip32.parse("xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", null);
// Derive a child key and serialize it as an xpub
let derived : ?Text = do ? {
rootKey!.derivePath(#text "m/1/2/3")!.serialize();
};
assert derived != null;
Bech32:
import Bech32 "mo:bitcoin/Bech32";
let encoded : Text = Bech32.encode("bc", [0x00, 0x01, 0x02], #BECH32);
let decoded = Bech32.decode("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4");
SegWit:
import Segwit "mo:bitcoin/Segwit";
// A version-0 witness program of 20 bytes (P2WPKH)
let program : Segwit.WitnessProgram = {
version = 0;
program = [0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6];
};
let address = Segwit.encode("bc", program);
let decoded = Segwit.decode("BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4");
Run all tests:
mops testBenchmark files are under the bench/ directory and cover encoding
(Base58/Base58Check/Bech32), hashing and HMAC, BIP32 derivation, EC arithmetic,
ECDSA verification, and Bitcoin transaction building and sighash. They use the
bench-helper mops package.
Run all benchmarks locally:
mops benchSince 0.2.2 the benchmarks are compiled with moc 2.0.0 and optimized with the
wasm-opt post-pass ([optimize] in mops.toml). Their numbers are therefore
not comparable with those of earlier versions: the difference comes from the
measurement setup, not from a change in the code.
Format the code with:
npm install --no-save prettier prettier-plugin-motoko
npx prettier --plugin prettier-plugin-motoko --write '**/*.{mo,json,md}'To release a new version:
- Open a PR that bumps
versionin the[package]section ofmops.tomland adds a matching## [x.y.z]section toCHANGELOG.md. - Merge the PR. The
Release tagworkflow tags the merge commit asvx.y.zand creates the GitHub release from the changelog section. - The tag push starts the
Publish on Mopsworkflow, which publishes the package to Mops.
For details, including how to publish an existing tag again, see
RELEASING.md.