diff --git a/CHANGELOG.md b/CHANGELOG.md index d9a97f0..0ae76ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,14 @@ # Motoko `bitcoin` changelog -## 1.0.0 +## Next +* Optimize (de)serializations in Bech32, Base58, Segwit * Bugfix: Taproot sighash now uses actual transaction values instead of hardcoded locktime=0 and version=2 (#14) -* Migrate code from `base` to `core` +* *Breaking:* Reject BIP32 paths with double-slashes in `Bip32.mo` (bugfix) * *Breaking:* Remove `toBytes` function in `bitcoin/TxOutput.mo` (use class method instead) * *Breaking:* Add length assertions inside `Bech32.encode()` * *Breaking*: Lowercase character range in `Bech32.mo` was incorrect (bugfix) -* *Breaking:* Reject BIP32 paths with double-slashes in `Bip32.mo` (bugfix) +* Migrate code from `base` to `core` ## 0.1.1 diff --git a/src/Base58.mo b/src/Base58.mo index d75386c..55674d5 100755 --- a/src/Base58.mo +++ b/src/Base58.mo @@ -1,23 +1,25 @@ import Array "mo:core/Array"; -import Char "mo:core/Char"; +import Blob "mo:core/Blob"; +import Nat16 "mo:core/Nat16"; import Nat32 "mo:core/Nat32"; +import Nat64 "mo:core/Nat64"; import Nat8 "mo:core/Nat8"; +import Runtime "mo:core/Runtime"; import Text "mo:core/Text"; -import { type Iter } "mo:core/Types"; import VarArray "mo:core/VarArray"; module { // All alphanumeric characters except for "0", "I", "O", and "l". // prettier-ignore - private let base58Alphabet : [Char] = [ - '1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G', - 'H','J','K','L','M','N','P','Q','R','S','T','U','V','W','X','Y','Z', - 'a','b','c','d','e','f','g','h','i','j','k','m','n','o','p','q','r', - 's','t','u','v','w','x','y','z' + private let base58Alphabet : [Nat8] = [ + 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, + 72, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122 ]; // prettier-ignore - private let mapBase58 : [Nat8] = [ + private let mapBase58 : [Nat] = [ 255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255, 255, 0, @@ -36,167 +38,198 @@ module { 255,255,255,255,255,255,255,255, ]; + func arrayToText(arr : [Nat8]) : Text { + switch (Blob.fromArray(arr).decodeUtf8()) { + case (?t) t; + case null Runtime.trap("unreachable"); + }; + }; + // Convert the given Base58 input to Base256. - public func decode(input : Text) : [Nat8] { - let inputIter : Iter = input.chars(); - var current : ?Char = inputIter.next(); - var spaces : Nat = 0; - - // Skip leading spaces - label l loop { - switch (current) { - case (?' ') { - spaces := spaces + 1; - }; - case (_) { - break l; - }; - }; - current := inputIter.next(); + public func decode(input_ : Text) : [Nat8] { + let input : Blob = Text.encodeUtf8(input_); + let inputSize = input.size(); + var pos : Nat = 0; + + // Skip leading spaces. + while (pos < inputSize and input[pos] == 0x20) { + pos += 1; }; // Skip and count leading '1's. - var zeroes : Nat = 0; + let startPos = pos; + while (pos < inputSize and input[pos] == 0x31) { + pos += 1; + }; + let zeroes : Nat = pos - startPos; + + // Find end of base58 payload (before trailing spaces). + var endPos = inputSize; + while (endPos > pos and input[endPos - 1] == 0x20) { + endPos -= 1; + }; + let digitCount : Nat = endPos - pos; + + // Allocate base256 buffer: log(58)/log(256) ≈ 733/1000. + let size : Nat = digitCount * 733 / 1000 + 1; + let b256 : [var Nat16] = VarArray.repeat(0x00, size); var length : Nat = 0; - label l loop { - switch (current) { - case (?'1') { - zeroes := zeroes + 1; - }; - case (_) { - break l; - }; + // Process leading remainder digits (digitCount % 8) one at a time. + let remainder = digitCount % 8; + var rem : Nat = 0; + while (rem < remainder) { + var carry : Nat16 = Nat16.fromIntWrap(mapBase58[input[pos].toNat()]); + assert (carry != 0xff); + + var i : Nat = 0; + var j : Nat = size - 1; + label inner while (carry != 0 or i < length) { + carry +%= 58 * b256[j]; + b256[j] := (carry & 0xff); + carry >>= 8; + i += 1; + if (j == 0) break inner; + j -= 1; }; - current := inputIter.next(); + + assert (carry == 0); + length := i; + pos += 1; + rem += 1; }; - // Compute how many bytes are needed for the Base256 representation. We - // need log(58) / log(256) of one byte to represent a Base58 digit in - // Base256, which is approximately 733 / 1000. The input size is multiplied - // by this value and rounded up to get the total Base256 required size. - let size : Nat = (input.size() - zeroes - spaces) * 733 / 1000 + 1; - let b256 : [var Nat8] = VarArray.repeat(0x00, size); - - label l loop { - switch (current) { - case (?' ') { - break l; - }; - case (null) { - break l; - }; - case (?value) { - var carry : Nat = mapBase58[value.toNat32().toNat()].toNat(); - assert (carry != 0xff); - - var i : Nat = 0; - var b256Pointer : Nat = b256.size() - 1; - label reverseIter while (carry != 0 or i < length) { - - carry += 58 * b256[b256Pointer].toNat(); - b256[b256Pointer] := Nat8.fromNat(carry % 256); - carry /= 256; - i += 1; - - if (b256Pointer == 0) { - break reverseIter; - }; - b256Pointer -= 1; - }; - - assert (carry == 0); - length := i; - }; + // Process full batches of 8 digits: b256 = b256 * 58^8 + v. + // 58^8 = 128_063_081_718_016. Max carry < 2^55, fits in Nat64. + while (pos < endPos) { + let d0 = Nat64.fromIntWrap(mapBase58[input[pos].toNat()]); + let d1 = Nat64.fromIntWrap(mapBase58[input[pos + 1].toNat()]); + let d2 = Nat64.fromIntWrap(mapBase58[input[pos + 2].toNat()]); + let d3 = Nat64.fromIntWrap(mapBase58[input[pos + 3].toNat()]); + let d4 = Nat64.fromIntWrap(mapBase58[input[pos + 4].toNat()]); + let d5 = Nat64.fromIntWrap(mapBase58[input[pos + 5].toNat()]); + let d6 = Nat64.fromIntWrap(mapBase58[input[pos + 6].toNat()]); + let d7 = Nat64.fromIntWrap(mapBase58[input[pos + 7].toNat()]); + assert ( + d0 != 0xff and d1 != 0xff and d2 != 0xff and d3 != 0xff and d4 != 0xff and d5 != 0xff and d6 != 0xff and d7 != 0xff + ); + + var carry : Nat64 = (((((((d0 *% 58 +% d1) *% 58 +% d2) *% 58 +% d3) *% 58 +% d4) *% 58 +% d5) *% 58 +% d6) *% 58 +% d7); + + var i : Nat = 0; + var j : Nat = size - 1; + label inner while (carry != 0 or i < length) { + carry +%= 128_063_081_718_016 *% b256[j].toNat32().toNat64(); + b256[j] := (carry & 0xff).toNat32().toNat16(); + carry >>= 8; + i += 1; + if (j == 0) break inner; + j -= 1; }; - current := inputIter.next(); + + assert (carry == 0); + length := i; + pos += 8; }; // Skip trailing spaces. - label l loop { - switch (current) { - case (?' ') {}; - case (_) { - break l; - }; - }; - current := inputIter.next(); + while (pos < inputSize and input[pos] == 0x20) { + pos += 1; }; // Check all input was consumed. - assert (current == null); + assert (pos == inputSize); // Skip leading zeroes in base256 result. - var b256Pointer : Nat = size - length; - while (b256Pointer < b256.size() and b256[b256Pointer] == 0) { - b256Pointer += 1; + var start : Nat = size - length; + while (start < size and b256[start] == 0) { + start += 1; }; - let output = Array.tabulate( - zeroes + b256.size() - b256Pointer, + Array.tabulate( + zeroes + size - start, func(i) { - if (i < zeroes) { - 0x00; - } else { - b256[i + b256Pointer - zeroes]; - }; + if (i < zeroes) 0x00 else b256[i + start - zeroes].toNat8(); }, ); - - output; }; // Convert the given Base256 input to Base58. public func encode(input : [Nat8]) : Text { - var zeroes : Nat = 0; + let inputSize = input.size(); var length : Nat = 0; - var inputPointer : Nat = 0; + var pos : Nat = 0; // Skip & count leading zeroes. - while (zeroes < input.size() and input[inputPointer] == 0) { - zeroes += 1; - inputPointer += 1; + while (pos < inputSize and input[pos] == 0) { + pos += 1; }; + let zeroes : Nat = pos; // Allocate enough space in big-endian base58 representation: // log(256) / log(58), rounded up. - let size : Nat = (input.size() - inputPointer) * 138 / 100 + 1; - let b58 : [var Nat8] = VarArray.repeat(0, size); + let bytesCount : Nat = inputSize - pos; + let size : Nat = bytesCount * 138 / 100 + 1; + let b58 : [var Nat16] = VarArray.repeat(0, size); + + // Process leading remainder bytes (bytesCount % 7) one at a time. + let remainder = bytesCount % 7; + var rem : Nat = 0; + while (rem < remainder) { + var carry : Nat16 = input[pos].toNat16(); + var i : Nat = 0; + var b58Pointer : Nat = size - 1; + label inner while (carry != 0 or i < length) { + carry +%= 256 *% b58[b58Pointer]; + b58[b58Pointer] := carry % 58; + carry /= 58; + i += 1; + if (b58Pointer == 0) break inner; + b58Pointer -= 1; + }; + assert (carry == 0); + length := i; + pos += 1; + rem += 1; + }; + + // Process full batches of 7 bytes: b58 = b58 * 256^7 + v. + // 256^7 = 72_057_594_037_927_936. Max carry < 2^62, fits in Nat64. + while (pos < inputSize) { + var carry : Nat64 = Nat64.fromIntWrap(input[pos].toNat()) << 48 | Nat64.fromIntWrap(input[pos + 1].toNat()) << 40 | Nat64.fromIntWrap(input[pos + 2].toNat()) << 32 | Nat64.fromIntWrap(input[pos + 3].toNat()) << 24 | Nat64.fromIntWrap(input[pos + 4].toNat()) << 16 | Nat64.fromIntWrap(input[pos + 5].toNat()) << 8 | Nat64.fromIntWrap(input[pos + 6].toNat()); - while (inputPointer < input.size()) { - var carry : Nat = input[inputPointer].toNat(); var i : Nat = 0; - // Apply "b58 = b58 * 256 + ch". - var b58Pointer : Nat = b58.size() - 1; - label reverseIter while (carry != 0 or i < length) { - carry += 256 * (b58[b58Pointer]).toNat(); - b58[b58Pointer] := Nat8.fromNat(carry % 58); + var b58Pointer : Nat = size - 1; + label inner while (carry != 0 or i < length) { + carry +%= 72_057_594_037_927_936 *% b58[b58Pointer].toNat32().toNat64(); + b58[b58Pointer] := (carry % 58).toNat32().toNat16(); carry /= 58; i += 1; - if (b58Pointer == 0) { - break reverseIter; - }; + if (b58Pointer == 0) break inner; b58Pointer -= 1; }; assert (carry == 0); length := i; - inputPointer += 1; + pos += 7; }; // Skip leading zeroes in base58 result. var b58Pointer : Nat = size - length; - while (b58Pointer < b58.size() and b58[b58Pointer] == 0) { b58Pointer += 1 }; + while (b58Pointer < size and b58[b58Pointer] == 0) { + b58Pointer += 1; + }; - let output = Array.tabulate( - zeroes + b58.size() - b58Pointer, + let outputBytes = Array.tabulate( + zeroes + size - b58Pointer, func(i) { if (i < zeroes) { - Char.fromNat32(0x31); + 0x31 : Nat8; } else { - base58Alphabet[b58[i + b58Pointer - zeroes].toNat()]; + base58Alphabet[b58[b58Pointer + i - zeroes].toNat()]; }; }, ); - Text.fromIter(output.values()); + + arrayToText(outputBytes); }; }; diff --git a/src/Bech32.mo b/src/Bech32.mo index 0d2825c..293f211 100644 --- a/src/Bech32.mo +++ b/src/Bech32.mo @@ -1,9 +1,10 @@ import Array "mo:core/Array"; import Blob "mo:core/Blob"; -import Char "mo:core/Char"; import Nat "mo:core/Nat"; +import Nat16 "mo:core/Nat16"; import Nat32 "mo:core/Nat32"; import Nat8 "mo:core/Nat8"; +import Runtime "mo:core/Runtime"; import Text "mo:core/Text"; import { type Result } "mo:core/Types"; import VarArray "mo:core/VarArray"; @@ -28,10 +29,10 @@ module { let CHARS_HIGHLIMIT : Nat8 = 0x7e; // prettier-ignore - let charset : [Char] = [ - 'q', 'p', 'z', 'r', 'y', '9', 'x', '8', 'g', 'f', '2', 't', 'v', 'd', 'w', - '0', 's', '3', 'j', 'n', '5', '4', 'k', 'h', 'c', 'e', '6', 'm', 'u', 'a', - '7', 'l' + let charset : [Nat8] = [ + 0x71, 0x70, 0x7a, 0x72, 0x79, 0x39, 0x78, 0x38, 0x67, 0x66, 0x32, 0x74, 0x76, 0x64, 0x77, + 0x30, 0x73, 0x33, 0x6a, 0x6e, 0x35, 0x34, 0x6b, 0x68, 0x63, 0x65, 0x36, 0x6d, 0x75, 0x61, + 0x37, 0x6c ]; // Mapping from ASCII to indices in charset for characters that exist in @@ -48,6 +49,13 @@ module { 3, 16, 11, 28, 12, 14, 6, 4, 2, 255, 255, 255, 255, 255 ]; + func arrayToText(arr : [Nat8]) : Text { + switch (Blob.fromArray(arr).decodeUtf8()) { + case (?t) t; + case null Runtime.trap("unreachable"); + }; + }; + // Encode input in Bech32 or a Bech32m. public func encode(hrp : Text, values : [Nat8], encoding : Encoding) : Text { assert hrp.size() > 0; @@ -62,16 +70,16 @@ module { let checksum : [Nat8] = createChecksum(encodedHrp, values, encoding); // hrp | '1' | values | checksum. - let output : [Char] = [ - hrp.toArray(), - ['1'], + let output : [Nat8] = [ + encodedHrp, + [0x31] : [Nat8], values.map(func x = charset[x.toNat()]), checksum.map(func x = charset[x.toNat()]), ].flatten(); assert output.size() <= 90; - Text.fromArray(output); + arrayToText(output); }; // Decode given text as Bech32 or Bech32m. @@ -195,9 +203,7 @@ module { Array.tabulate( 6, func(i) { - Nat8.fromIntWrap( - ((mod >> (5 * (5 - Nat32.fromIntWrap(i)))) & 31).toNat() - ); + ((mod >> (5 * (5 - Nat32.fromIntWrap(i)))) & 31).toNat16().toNat8(); }, ); }; @@ -225,8 +231,8 @@ module { var c : Nat32 = 1; for (value in values.values()) { - let c0 : Nat8 = Nat8.fromIntWrap((c >> 25).toNat()); - c := ((c & 0x1ffffff) << 5) ^ Nat32.fromIntWrap(value.toNat()); + let c0 : Nat8 = (c >> 25).toNat16().toNat8(); + c := ((c & 0x1ffffff) << 5) ^ value.toNat16().toNat32(); // Conditionally add in coefficients of the generator polynomial. if (c0 & 1 > 0) c ^= 0x3b6a57b2; diff --git a/src/ByteUtils.mo b/src/ByteUtils.mo index 67898f4..e6af3a1 100644 --- a/src/ByteUtils.mo +++ b/src/ByteUtils.mo @@ -17,6 +17,8 @@ module { reverse : Bool, ) : ?[Nat8] { do ? { + if (count == 0) return ?[]; + let readData : [var Nat8] = VarArray.repeat(0, count); if (reverse) { var nextReadIndex : Nat = count - 1; @@ -46,7 +48,7 @@ module { public func readLE16(data : Iter) : ?Nat16 { do ? { let (a, b) = (data.next()!, data.next()!); - Nat16.fromIntWrap(b.toNat()) << 8 | Nat16.fromIntWrap(a.toNat()); + b.toNat16() << 8 | a.toNat16(); }; }; @@ -55,7 +57,7 @@ module { public func readLE32(data : Iter) : ?Nat32 { do ? { let (a, b, c, d) = (data.next()!, data.next()!, data.next()!, data.next()!); - Nat32.fromIntWrap(d.toNat()) << 24 | Nat32.fromIntWrap(c.toNat()) << 16 | Nat32.fromIntWrap(b.toNat()) << 8 | Nat32.fromIntWrap(a.toNat()); + d.toNat16().toNat32() << 24 | c.toNat16().toNat32() << 16 | b.toNat16().toNat32() << 8 | a.toNat16().toNat32(); }; }; @@ -74,7 +76,7 @@ module { data.next()!, ); - Nat64.fromIntWrap(h.toNat()) << 56 | Nat64.fromIntWrap(g.toNat()) << 48 | Nat64.fromIntWrap(f.toNat()) << 40 | Nat64.fromIntWrap(e.toNat()) << 32 | Nat64.fromIntWrap(d.toNat()) << 24 | Nat64.fromIntWrap(c.toNat()) << 16 | Nat64.fromIntWrap(b.toNat()) << 8 | Nat64.fromIntWrap(a.toNat()); + h.toNat16().toNat32().toNat64() << 56 | g.toNat16().toNat32().toNat64() << 48 | f.toNat16().toNat32().toNat64() << 40 | e.toNat16().toNat32().toNat64() << 32 | d.toNat16().toNat32().toNat64() << 24 | c.toNat16().toNat32().toNat64() << 16 | b.toNat16().toNat32().toNat64() << 8 | a.toNat16().toNat32().toNat64(); }; }; diff --git a/src/Common.mo b/src/Common.mo index ef28ca5..1dd3e79 100644 --- a/src/Common.mo +++ b/src/Common.mo @@ -7,7 +7,7 @@ import Nat8 "mo:core/Nat8"; module { // Read big endian 32-bit natural number starting at offset. public func readBE32(bytes : [Nat8], offset : Nat) : Nat32 { - Nat32.fromIntWrap(bytes[offset + 0].toNat()) << 24 | Nat32.fromIntWrap(bytes[offset + 1].toNat()) << 16 | Nat32.fromIntWrap(bytes[offset + 2].toNat()) << 8 | Nat32.fromIntWrap(bytes[offset + 3].toNat()); + bytes[offset + 0].toNat16().toNat32() << 24 | bytes[offset + 1].toNat16().toNat32() << 16 | bytes[offset + 2].toNat16().toNat32() << 8 | bytes[offset + 3].toNat16().toNat32(); }; // Read big endian 64-bit natural number starting at offset. @@ -15,7 +15,7 @@ module { let first : Nat32 = readBE32(bytes, offset); let second : Nat32 = readBE32(bytes, offset + 4); - Nat64.fromIntWrap(first.toNat()) << 32 | Nat64.fromIntWrap(second.toNat()); + first.toNat64() << 32 | second.toNat64(); }; // Read big endian 128-bit natural number starting at offset. @@ -71,7 +71,7 @@ module { // Read little endian 32-bit natural number starting at offset. public func readLE32(bytes : [Nat8], offset : Nat) : Nat32 { - Nat32.fromIntWrap(bytes[offset + 3].toNat()) << 24 | Nat32.fromIntWrap(bytes[offset + 2].toNat()) << 16 | Nat32.fromIntWrap(bytes[offset + 1].toNat()) << 8 | Nat32.fromIntWrap(bytes[offset + 0].toNat()); + bytes[offset + 3].toNat16().toNat32() << 24 | bytes[offset + 2].toNat16().toNat32() << 16 | bytes[offset + 1].toNat16().toNat32() << 8 | bytes[offset + 0].toNat16().toNat32(); }; // Write given value as 16-bit little endian into array starting at offset. diff --git a/src/Segwit.mo b/src/Segwit.mo index 7eb85bc..649f520 100644 --- a/src/Segwit.mo +++ b/src/Segwit.mo @@ -1,9 +1,10 @@ -import List "mo:core/List"; +import Array "mo:core/Array"; import Nat "mo:core/Nat"; +import Nat16 "mo:core/Nat16"; import Nat32 "mo:core/Nat32"; import Nat8 "mo:core/Nat8"; -import Runtime "mo:core/Runtime"; -import { type Result; type Iter } "mo:core/Types"; +import { type Result } "mo:core/Types"; +import VarArray "mo:core/VarArray"; import Bech32 "Bech32"; @@ -17,14 +18,9 @@ module { // Convert a Witness Program to a SegWit Address. public func encode(hrp : Text, { version; program } : WitnessProgram) : Result { - let bech32Input = List.empty(); - bech32Input.add(version); - - switch (convertBits(program.values(), bech32Input, 8, 5, true)) { - case (#err(msg)) { - return #err(msg); - }; - case _ {}; + let converted = switch (convertBits(program, 0, 8, 5, true)) { + case (#err(msg)) return #err(msg); + case (#ok(c)) c; }; let encoding : Bech32.Encoding = if (version > 0) { @@ -35,7 +31,7 @@ module { let bech32Result : Text = Bech32.encode( hrp, - bech32Input.toArray(), + [[version] : [Nat8], converted].flatten(), encoding, ); @@ -67,69 +63,61 @@ module { return #err("Invalid data length."); }; - // Split into version and program. - let dataIter : Iter = data.values(); - let version : Nat8 = switch (dataIter.next()) { - case (?val) { - val; - }; - case _ { - Runtime.trap("unreachable"); - }; + let version : Nat8 = data[0]; + + if (version > 16) { + return #err("Invalid witness version."); }; - let convertedData = List.empty(); - switch (convertBits(dataIter, convertedData, 5, 8, false)) { - case (#ok) { - let convertedDataSize : Nat = convertedData.size(); - - if (convertedDataSize < 2 or convertedDataSize > 40) { - return #err("Wrong output size."); - }; - - if (data[0] > 16) { - return #err("Invalid witness version."); - }; - - if ( - data[0] == 0 and convertedDataSize != 20 and convertedDataSize != 32 - ) { - return #err("Program size does not match witness version."); - }; - - if ( - data[0] == 0 and encoding != #BECH32 or - data[0] != 0 and encoding != #BECH32M - ) { - return #err("Encoding does not match witness version."); - }; - - return #ok(decodedHrp, { version; program = convertedData.toArray() }); - }; - case _ { - return #err("Convert bits failed."); - }; + let convertedData = switch (convertBits(data, 1, 5, 8, false)) { + case (#err(msg)) return #err(msg); + case (#ok(d)) d; }; + + let convertedDataSize : Nat = convertedData.size(); + + if (convertedDataSize < 2 or convertedDataSize > 40) { + return #err("Wrong output size."); + }; + + if ( + version == 0 and convertedDataSize != 20 and convertedDataSize != 32 + ) { + return #err("Program size does not match witness version."); + }; + + if ( + version == 0 and encoding != #BECH32 or + version != 0 and encoding != #BECH32M + ) { + return #err("Encoding does not match witness version."); + }; + + #ok(decodedHrp, { version; program = convertedData }); }; // Convert between two bases that are power of 2. func convertBits( - data : Iter, - output : List.List, + data : [Nat8], + start : Nat, from : Nat32, to : Nat32, pad : Bool, - ) : Result<(), Text> { + ) : Result<[Nat8], Text> { var acc : Nat32 = 0; var bits : Nat32 = 0; let maxv : Nat32 = (1 << to) - 1; + let dataSize = data.size(); + let output = VarArray.repeat(0, (dataSize - start) * from.toNat() / to.toNat() + 1); + var outputLen : Nat = 0; - for (value in data) { - let v : Nat32 = Nat32.fromIntWrap(value.toNat()); + var pos = start; + while (pos < dataSize) { + let v : Nat32 = data[pos].toNat16().toNat32(); if ((v >> from) != 0) { - return #err("Invalid input value: " # value.toNat().toText()); + return #err("Invalid input value: " # data[pos].toNat().toText()); }; acc := (acc << from) | v; @@ -137,26 +125,21 @@ module { while (bits >= to) { bits -= to; - output.add( - Nat8.fromIntWrap( - ((acc >> bits) & maxv).toNat() - ) - ); + output[outputLen] := ((acc >> bits) & maxv).toNat16().toNat8(); + outputLen += 1; }; + pos += 1; }; if (pad) { if (bits > 0) { - output.add( - Nat8.fromIntWrap( - ((acc << (to - bits)) & maxv).toNat() - ) - ); + output[outputLen] := ((acc << (to - bits)) & maxv).toNat16().toNat8(); + outputLen += 1; }; } else if (bits >= from or ((acc << (to - bits)) & maxv) != 0) { return #err("Invalid Padding"); }; - return #ok; + #ok(Array.tabulate(outputLen, func(i) = output[i])); }; }; diff --git a/src/ec/Jacobi.mo b/src/ec/Jacobi.mo index 8d4c2f7..44cfbf8 100644 --- a/src/ec/Jacobi.mo +++ b/src/ec/Jacobi.mo @@ -236,12 +236,14 @@ module { let (XX, YY) = (X1 * X1 % p, Y1 * Y1 % p); let YYYY : Int = YY * YY % p; let ZZ : Int = Z1 * Z1 % p; - let S : Int = 2 * ((X1 + YY) ** 2 - XX - YYYY) % p; - let M : Int = (3 * XX + a * ZZ * ZZ) % p; + let S : Int = (X1 + YY) |> 2 * (_ * _ - XX - YYYY) % p; + let M : Int = if (a == 0) { 3 * XX % p } else { + (3 * XX + a * ZZ * ZZ) % p; + }; let T : Int = (M * M - 2 * S) % p; let Y3 = (M * (S - T) - 8 * YYYY) % p; - let Z3 = ((Y1 + Z1) ** 2 - YY - ZZ) % p; + let Z3 = (Y1 + Z1) |> (_ * _ - YY - ZZ) % p; return (T, Y3, Z3); }; @@ -306,7 +308,7 @@ module { }; let V : Int = X1 * I; - let X3 : Int = (r ** 2 - J - 2 * V) % p; + let X3 : Int = (r * r - J - 2 * V) % p; let Y3 : Int = (r * (V - X3) - 2 * Y1 * J) % p; let Z3 : Int = 2 * H % p;