Merge pull request #1 from KaiWitt/dev

Implement XOR
This commit is contained in:
Kai 2021-08-24 09:39:51 +02:00 committed by GitHub
commit 723ebe1ad8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 303 additions and 1 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
todos.txt

64
Cargo.lock generated Normal file
View File

@ -0,0 +1,64 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "bip39"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e89470017230c38e52b82b3ee3f530db1856ba1d434e3a67a3456a8a8dec5f"
dependencies = [
"bitcoin_hashes",
"rand_core",
"serde",
"unicode-normalization",
]
[[package]]
name = "bitcoin_hashes"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ce18265ec2324ad075345d5814fbeed4f41f0a660055dc78840b74d19b874b1"
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "seed-xor"
version = "0.1.0"
dependencies = [
"bip39",
]
[[package]]
name = "serde"
version = "1.0.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1056a0db1978e9dbf0f6e4fca677f6f9143dc1c19de346f22cac23e422196834"
[[package]]
name = "smallvec"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
dependencies = [
"maybe-uninit",
]
[[package]]
name = "unicode-normalization"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf"
dependencies = [
"smallvec",
]

17
Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "seed-xor"
version = "0.1.0"
edition = "2018"
authors = ["KaiWitt <kaiwitt@protonmail.com>"]
description = "XOR bip39 mnemonics."
readme = "README.md"
repository = "https://github.com/KaiWitt/seed-xor"
license = "MIT"
keywords = ["bitcoin", "seed", "mnemonic", "bip39", "xor"]
categories = ["cryptography::cryptocurrencies"]
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bip39 = "1.0.1"

View File

@ -1 +1,35 @@
# seed-xor
seed-xor builds on top of [rust-bip39](https://github.com/rust-bitcoin/rust-bip39/)
and lets you XOR bip39 mnemonics as defined in [Coldcards docs](https://github.com/Coldcard/firmware/blob/master/docs/seed-xor.md).
It is also possible to XOR mnemonics with differing numbers of words.
For this the shorter one will be extended with 0s during the XOR calculation.
## Example
```rust
use seed_xor::Mnemonic;
use std::str::FromStr;
// Coldcard example: https://github.com/Coldcard/firmware/blob/master/docs/seed-xor.md
let a_str = "romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room";
let b_str = "lion misery divide hurry latin fluid camp advance illegal lab pyramid unaware eager fringe sick camera series noodle toy crowd jeans select depth lounge";
let c_str = "vault nominee cradle silk own frown throw leg cactus recall talent worry gadget surface shy planet purpose coffee drip few seven term squeeze educate";
let result_str = "silent toe meat possible chair blossom wait occur this worth option bag nurse find fish scene bench asthma bike wage world quit primary indoor";
// Mnemonic is a wrapper for bip39::Mnemonic which implements the XOR operation `^`.
// Mnemonics can also be created from entropy.
let a = Mnemonic::from_str(a_str).unwrap();
let b = Mnemonic::from_str(b_str).unwrap();
let c = Mnemonic::from_str(c_str).unwrap();
let result = Mnemonic::from_str(result_str).unwrap();
assert_eq!(result, a ^ b ^ c);
```
## Useful resources
- Coldcard docs: https://github.com/Coldcard/firmware/blob/master/docs/seed-xor.md
- Easy bip39 mnemonic explanation: https://learnmeabitcoin.com/technical/mnemonic

185
src/lib.rs Normal file
View File

@ -0,0 +1,185 @@
//! # seed-xor
//!
//! seed-xor builds on top of [rust-bip39](https://github.com/rust-bitcoin/rust-bip39/)
//! and lets you XOR bip39 mnemonics as defined in [Coldcards docs](https://github.com/Coldcard/firmware/blob/master/docs/seed-xor.md).
//!
//!
//! It is also possible to XOR mnemonics with differing numbers of words.
//! For this the shorter one will be extended with 0s during the XOR calculation.
//!
//!
//! ## Example
//!
//! ```rust
//! use seed_xor::Mnemonic;
//! use std::str::FromStr;
//!
//! // Coldcard example: https://github.com/Coldcard/firmware/blob/master/docs/seed-xor.md
//! let a_str = "romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room";
//! let b_str = "lion misery divide hurry latin fluid camp advance illegal lab pyramid unaware eager fringe sick camera series noodle toy crowd jeans select depth lounge";
//! let c_str = "vault nominee cradle silk own frown throw leg cactus recall talent worry gadget surface shy planet purpose coffee drip few seven term squeeze educate";
//! let result_str = "silent toe meat possible chair blossom wait occur this worth option bag nurse find fish scene bench asthma bike wage world quit primary indoor";
//!
//! // Mnemonic is a wrapper for bip39::Mnemonic which implements the XOR operation `^`.
//! // Mnemonics can also be created from entropy.
//! let a = Mnemonic::from_str(a_str).unwrap();
//! let b = Mnemonic::from_str(b_str).unwrap();
//! let c = Mnemonic::from_str(c_str).unwrap();
//! let result = Mnemonic::from_str(result_str).unwrap();
//!
//! assert_eq!(result, a ^ b ^ c);
//! ```
//!
use std::{
fmt,
ops::{BitXor, BitXorAssign},
str::FromStr,
};
use bip39::Mnemonic as Inner;
/// Wrapper for a [bip39::Mnemonic] which is aliased as `Inner`.
#[derive(Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
pub struct Mnemonic {
/// Actual [bip39::Mnemonic] which is wrapped to be able to implement the XOR operator.
inner: Inner,
}
impl Mnemonic {
/// Private constructor.
fn new(inner: Inner) -> Self {
Mnemonic { inner }
}
/// Access the private inner [bip39::Mnemonic] for more functionality.
pub fn inner(&self) -> &Inner {
&self.inner
}
/// Wrapper for the same method as in [bip39::Mnemonic].
pub fn from_entropy(entropy: &[u8]) -> Result<Self, bip39::Error> {
match Inner::from_entropy(entropy) {
Ok(inner) => Ok(Mnemonic::new(inner)),
Err(err) => Err(err),
}
}
/// XOR two [Mnemonic]s without consuming them.
/// If consumption is not of relevance the XOR operator `^` and XOR assigner `^=` can be used as well.
fn xor(&self, rhs: &Self) -> Self {
let mut entropy = self.inner.to_entropy();
let xor_values = rhs.inner.to_entropy();
// XOR each Byte
entropy
.iter_mut()
.zip(xor_values.iter())
.for_each(|(a, b)| *a ^= b);
// Extend entropy with values of xor_values if it has a shorter entropy length.
if entropy.len() < xor_values.len() {
entropy.extend(xor_values.iter().skip(entropy.len()))
}
// We unwrap here because entropy has either as many Bytes
// as self or rhs and both are valid mnemonics.
Mnemonic::from_entropy(&entropy).unwrap()
}
}
impl FromStr for Mnemonic {
type Err = bip39::Error;
fn from_str(mnemonic: &str) -> Result<Self, <Self as FromStr>::Err> {
match Inner::from_str(mnemonic) {
Ok(inner) => Ok(Mnemonic::new(inner)),
Err(err) => Err(err),
}
}
}
impl fmt::Display for Mnemonic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (i, word) in self.inner.word_iter().enumerate() {
if i > 0 {
f.write_str(" ")?;
}
f.write_str(word)?;
}
Ok(())
}
}
impl BitXor for Mnemonic {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self::Output {
self.xor(&rhs)
}
}
impl BitXorAssign for Mnemonic {
fn bitxor_assign(&mut self, rhs: Self) {
*self = self.xor(&rhs)
}
}
#[cfg(test)]
mod tests {
use crate::Mnemonic;
use std::str::FromStr;
#[test]
fn seed_xor_works() {
// Coldcard example: https://github.com/Coldcard/firmware/blob/master/docs/seed-xor.md
let a_str = "romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room";
let b_str = "lion misery divide hurry latin fluid camp advance illegal lab pyramid unaware eager fringe sick camera series noodle toy crowd jeans select depth lounge";
let c_str = "vault nominee cradle silk own frown throw leg cactus recall talent worry gadget surface shy planet purpose coffee drip few seven term squeeze educate";
let result_str = "silent toe meat possible chair blossom wait occur this worth option bag nurse find fish scene bench asthma bike wage world quit primary indoor";
let a = Mnemonic::from_str(a_str).unwrap();
let b = Mnemonic::from_str(b_str).unwrap();
let c = Mnemonic::from_str(c_str).unwrap();
let result = Mnemonic::from_str(result_str).unwrap();
assert_eq!(result, a.clone() ^ b.clone() ^ c.clone());
assert_eq!(result, b ^ c ^ a); // Commutative
}
#[test]
fn seed_xor_assignment_works() {
// Coldcard example: https://github.com/Coldcard/firmware/blob/master/docs/seed-xor.md
let a_str = "romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room";
let b_str = "lion misery divide hurry latin fluid camp advance illegal lab pyramid unaware eager fringe sick camera series noodle toy crowd jeans select depth lounge";
let c_str = "vault nominee cradle silk own frown throw leg cactus recall talent worry gadget surface shy planet purpose coffee drip few seven term squeeze educate";
let result_str = "silent toe meat possible chair blossom wait occur this worth option bag nurse find fish scene bench asthma bike wage world quit primary indoor";
let a = Mnemonic::from_str(a_str).unwrap();
let b = Mnemonic::from_str(b_str).unwrap();
let c = Mnemonic::from_str(c_str).unwrap();
let result = Mnemonic::from_str(result_str).unwrap();
let mut assigned = a.xor(&b); // XOR without consuming
assigned ^= c;
assert_eq!(result, assigned);
}
#[test]
fn seed_xor_with_different_lengths_works() {
// Coldcard example: https://github.com/Coldcard/firmware/blob/master/docs/seed-xor.md
// but truncated mnemonics with correct last word.
let str_24 = "romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room";
let str_16 = "lion misery divide hurry latin fluid camp advance illegal lab pyramid unaware eager fringe sick camera series number";
let str_12 = "vault nominee cradle silk own frown throw leg cactus recall talent wisdom";
let result_str = "silent toe meat possible chair blossom wait occur this worth option aware since milk mother grace rocket cement recall obey exchange recycle dragon rocket";
let w_24 = Mnemonic::from_str(str_24).unwrap();
let w_16 = Mnemonic::from_str(str_16).unwrap();
let w_12 = Mnemonic::from_str(str_12).unwrap();
let result = Mnemonic::from_str(result_str).unwrap();
assert_eq!(result, w_24.clone() ^ w_16.clone() ^ w_12.clone());
assert_eq!(result, w_12 ^ w_24 ^ w_16); // Commutative
}
}