Skip to content

Commit 8a504c0

Browse files
committed
chore: move bc_ur to own package
1 parent 363b3e1 commit 8a504c0

23 files changed

+3242
-18
lines changed

packages/bc_ur/.gitignore

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Dart-specific
2+
.dart_tool/
3+
.packages
4+
build/
5+
pubspec.lock
6+
7+
# IDE-specific
8+
.idea/
9+
.vscode/
10+
11+
# Flutter/Dart package-specific
12+
*.iml
13+
*.lock
14+
*.log
15+
.flutter-plugins
16+
.flutter-plugins-dependencies
17+
18+
# macOS-specific
19+
.DS_Store
20+
21+
# Windows-specific
22+
Thumbs.db
23+
24+
# Coverage reports
25+
coverage/
26+
27+
# Temporary files
28+
*.tmp
29+
*.temp
30+
*.swp
31+
32+
# Generated documentation
33+
doc/api/
34+
35+
# Avoid committing generated JavaScript files
36+
*.dart.js
37+
*.info.json
38+
*.js
39+
*.js_
40+
*.js.deps
41+
*.js.map
42+
43+
# Avoid committing generated files for pub
44+
.pub/
45+
.pub-cache/
46+
47+
# Avoid committing build outputs
48+
*.aot
49+
*.dill
50+
*.dill.track.dill
51+
*.dill.incremental.dill
52+
53+
# Avoid committing test failures
54+
failures/
55+
56+
# Avoid committing performance profiling data
57+
*.timeline

packages/bc_ur/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Aleksandr Bukata
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/bc_ur/lib/bytewords.dart

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import 'dart:typed_data';
2+
import 'package:ur/utils.dart';
3+
import 'package:collection/collection.dart';
4+
5+
const String BYTEWORDS =
6+
'ableacidalsoapexaquaarchatomauntawayaxisbackbaldbarnbeltbetabiasbluebodybragbrewbulbbuzzcalmcashcatschefcityclawcodecolacookcostcruxcurlcuspcyandarkdatadaysdelidicedietdoordowndrawdropdrumdulldutyeacheasyechoedgeepicevenexamexiteyesfactfairfernfigsfilmfishfizzflapflewfluxfoxyfreefrogfuelfundgalagamegeargemsgiftgirlglowgoodgraygrimgurugushgyrohalfhanghardhawkheathelphighhillholyhopehornhutsicedideaidleinchinkyintoirisironitemjadejazzjoinjoltjowljudojugsjumpjunkjurykeepkenokeptkeyskickkilnkingkitekiwiknoblamblavalazyleaflegsliarlimplionlistlogoloudloveluaulucklungmainmanymathmazememomenumeowmildmintmissmonknailnavyneednewsnextnoonnotenumbobeyoboeomitonyxopenovalowlspaidpartpeckplaypluspoempoolposepuffpumapurrquadquizraceramprealredorichroadrockroofrubyruinrunsrustsafesagascarsetssilkskewslotsoapsolosongstubsurfswantacotasktaxitenttiedtimetinytoiltombtoystriptunatwinuglyundouniturgeuservastveryvetovialvibeviewvisavoidvowswallwandwarmwaspwavewaxywebswhatwhenwhizwolfworkyankyawnyellyogayurtzapszerozestzinczonezoom';
7+
8+
List<int>? _wordArray;
9+
10+
enum Style {
11+
standard,
12+
uri,
13+
minimal,
14+
}
15+
16+
int decodeWord(String word, int wordLen) {
17+
if (word.length != wordLen) {
18+
throw ArgumentError('Invalid Bytewords.');
19+
}
20+
21+
const int dim = 26;
22+
23+
// Since the first and last letters of each Byteword are unique,
24+
// we can use them as indexes into a two-dimensional lookup table.
25+
// This table is generated lazily.
26+
if (_wordArray == null) {
27+
_wordArray = List.generate(dim * dim, (i) => -1);
28+
29+
for (int i = 0; i < 256; i++) {
30+
int bytewordOffset = i * 4;
31+
int x = BYTEWORDS[bytewordOffset].codeUnitAt(0) - 'a'.codeUnitAt(0);
32+
int y = BYTEWORDS[bytewordOffset + 3].codeUnitAt(0) - 'a'.codeUnitAt(0);
33+
int arrayOffset = y * dim + x;
34+
_wordArray![arrayOffset] = i;
35+
}
36+
}
37+
38+
// If the coordinates generated by the first and last letters are out of bounds,
39+
// or the lookup table contains -1 at the coordinates, then the word is not valid.
40+
int x = word[0].toLowerCase().codeUnitAt(0) - 'a'.codeUnitAt(0);
41+
int y = word[wordLen == 4 ? 3 : 1].toLowerCase().codeUnitAt(0) -
42+
'a'.codeUnitAt(0);
43+
if (x < 0 || x >= dim || y < 0 || y >= dim) {
44+
throw ArgumentError('Invalid Bytewords.');
45+
}
46+
47+
int value = _wordArray![y * dim + x];
48+
if (value == -1) {
49+
throw ArgumentError('Invalid Bytewords.');
50+
}
51+
52+
// If we're decoding a full four-letter word, verify that the two middle letters are correct.
53+
if (wordLen == 4) {
54+
int bytewordOffset = value * 4;
55+
String c1 = word[1].toLowerCase();
56+
String c2 = word[2].toLowerCase();
57+
if (c1 != BYTEWORDS[bytewordOffset + 1] ||
58+
c2 != BYTEWORDS[bytewordOffset + 2]) {
59+
throw ArgumentError('Invalid Bytewords.');
60+
}
61+
}
62+
63+
// Successful decode.
64+
return value;
65+
}
66+
67+
String getWord(int index) {
68+
int bytewordOffset = index * 4;
69+
return BYTEWORDS.substring(bytewordOffset, bytewordOffset + 4);
70+
}
71+
72+
String getMinimalWord(int index) {
73+
int bytewordOffset = index * 4;
74+
return BYTEWORDS[bytewordOffset] + BYTEWORDS[bytewordOffset + 3];
75+
}
76+
77+
String encode(Uint8List buf, String separator) {
78+
return buf.map((byte) => getWord(byte)).join(separator);
79+
}
80+
81+
Uint8List addCrc(Uint8List buf) {
82+
Uint8List crcBuf = crc32Bytes(buf);
83+
return Uint8List.fromList([...buf, ...crcBuf]);
84+
}
85+
86+
String encodeWithSeparator(Uint8List buf, String separator) {
87+
Uint8List crcBuf = addCrc(buf);
88+
return encode(crcBuf, separator);
89+
}
90+
91+
String encodeMinimal(Uint8List buf) {
92+
Uint8List crcBuf = addCrc(buf);
93+
return crcBuf.map((byte) => getMinimalWord(byte)).join();
94+
}
95+
96+
Uint8List decode(String s, String separator, int wordLen) {
97+
List<String> words;
98+
if (wordLen == 4) {
99+
words = s.split(separator);
100+
} else {
101+
words = partition(s, 2);
102+
}
103+
104+
Uint8List buf = Uint8List(words.length);
105+
for (int i = 0; i < words.length; i++) {
106+
buf[i] = decodeWord(words[i], wordLen);
107+
}
108+
109+
if (buf.length < 5) {
110+
throw ArgumentError('Invalid Bytewords.');
111+
}
112+
113+
// Validate checksum
114+
Uint8List body = buf.sublist(0, buf.length - 4);
115+
Uint8List bodyChecksum = buf.sublist(buf.length - 4);
116+
Uint8List checksum = crc32Bytes(body);
117+
Function listEquals = const ListEquality().equals;
118+
if (!listEquals(checksum, bodyChecksum)) {
119+
throw ArgumentError('Invalid Bytewords.');
120+
}
121+
122+
return body;
123+
}
124+
125+
String encodeStyle(Style style, Uint8List bytes) {
126+
switch (style) {
127+
case Style.standard:
128+
return encodeWithSeparator(bytes, ' ');
129+
case Style.uri:
130+
return encodeWithSeparator(bytes, '-');
131+
case Style.minimal:
132+
return encodeMinimal(bytes);
133+
default:
134+
throw ArgumentError('Invalid Bytewords style.');
135+
}
136+
}
137+
138+
Uint8List decodeStyle(Style style, String str) {
139+
switch (style) {
140+
case Style.standard:
141+
return decode(str, ' ', 4);
142+
case Style.uri:
143+
return decode(str, '-', 4);
144+
case Style.minimal:
145+
return decode(str, '', 2);
146+
default:
147+
throw ArgumentError('Invalid Bytewords style.');
148+
}
149+
}

packages/ndk/example/cashu_animated_qr_example.dart renamed to packages/bc_ur/lib/cashu_animated_qr_example.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// ignore_for_file: avoid_print
22
import 'package:ndk/domain_layer/entities/cashu/cashu_proof.dart';
33
import 'package:ndk/domain_layer/entities/cashu/cashu_token.dart';
4-
import 'package:ndk/domain_layer/usecases/cashu/cashu_token_ur_encoder.dart';
4+
import 'cashu_token_ur_encoder.dart';
55

66
/// Example demonstrating NUT-16 Animated QR codes using UR encoding
77
///

packages/ndk/lib/domain_layer/usecases/cashu/cashu_token_ur_encoder.dart renamed to packages/bc_ur/lib/cashu_token_ur_encoder.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import 'dart:typed_data';
22

33
import 'package:cbor/cbor.dart';
4+
import 'package:ndk/entities.dart';
5+
import 'package:ndk/ndk.dart';
46
import 'package:ur/ur.dart';
57
import 'package:ur/ur_encoder.dart';
68
import 'package:ur/ur_decoder.dart';
79

8-
import '../../../shared/logger/logger.dart';
9-
import '../../entities/cashu/cashu_token.dart';
10-
1110
/// Encoder and decoder for Cashu tokens using UR (Uniform Resources) format.
1211
/// This implements NUT-16 for animated QR codes support.
1312
///

0 commit comments

Comments
 (0)