Skip to content

Commit

Permalink
Merge branch 'sp'
Browse files Browse the repository at this point in the history
benma committed Sep 18, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents f819645 + 3b91825 commit 83cd6c8
Showing 197 changed files with 58,327 additions and 104 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
## Firmware

### [Unreleased]
- Bitcoin: add support for sending to silent payment (BIP-352) addresses
- Bitcoin: add support for regtest

### 9.20.0
2 changes: 1 addition & 1 deletion external/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ string(REPLACE "-mfpu=fpv4-sp-d16" "" MODIFIED_C_FLAGS ${MODIFIED_C_FLAGS_TMP})
# wally-core

# configure flags for secp256k1 bundled in libwally core, to reduce memory consumption
set(LIBWALLY_SECP256k1_FLAGS --with-ecmult-window=2 --with-ecmult-gen-precision=2 --enable-ecmult-static-precomputation --enable-module-schnorrsig)
set(LIBWALLY_SECP256k1_FLAGS --with-ecmult-window=2 --with-ecmult-gen-precision=2 --enable-ecmult-static-precomputation --enable-module-schnorrsig --enable-module-ecdsa-adaptor)
set(LIBWALLY_CONFIGURE_FLAGS --enable-static --disable-shared --disable-tests ${LIBWALLY_SECP256k1_FLAGS})
if(SANITIZE_ADDRESS)
set(LIBWALLY_CFLAGS "-fsanitize=address")
11 changes: 11 additions & 0 deletions messages/btc.proto
Original file line number Diff line number Diff line change
@@ -114,6 +114,7 @@ message BTCSignInitRequest {
SAT = 1;
}
FormatUnit format_unit = 8;
bool contains_silent_payment_outputs = 9;
}

message BTCSignNextResponse {
@@ -137,6 +138,9 @@ message BTCSignNextResponse {
// Previous tx's input/output index in case of PREV_INPUT or PREV_OUTPUT, for the input at `index`.
uint32 prev_index = 5;
AntiKleptoSignerCommitment anti_klepto_signer_commitment = 6;
// Generated output. The host *must* verify its correctness using `silent_payment_dleq_proof`.
bytes generated_output_pkscript = 7;
bytes silent_payment_dleq_proof = 8;
}

message BTCSignInputRequest {
@@ -160,6 +164,10 @@ enum BTCOutputType {
}

message BTCSignOutputRequest {
// https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki
message SilentPayment {
string address = 1;
}
bool ours = 1;
BTCOutputType type = 2; // if ours is false
// 20 bytes for p2pkh, p2sh, pw2wpkh. 32 bytes for p2wsh.
@@ -169,6 +177,9 @@ message BTCSignOutputRequest {
// If ours is true. References a script config from BTCSignInitRequest
uint32 script_config_index = 6;
optional uint32 payment_request_index = 7;
// If provided, `type` and `payload` is ignored. The generated output pkScript is returned in
// BTCSignNextResponse. `contains_silent_payment_outputs` in the init request must be true.
SilentPayment silent_payment = 8;
}

message BTCScriptConfigRegistration {
98 changes: 50 additions & 48 deletions py/bitbox02/bitbox02/communication/generated/btc_pb2.py

Large diffs are not rendered by default.

38 changes: 34 additions & 4 deletions py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi
Original file line number Diff line number Diff line change
@@ -292,6 +292,7 @@ class BTCSignInitRequest(google.protobuf.message.Message):
NUM_OUTPUTS_FIELD_NUMBER: builtins.int
LOCKTIME_FIELD_NUMBER: builtins.int
FORMAT_UNIT_FIELD_NUMBER: builtins.int
CONTAINS_SILENT_PAYMENT_OUTPUTS_FIELD_NUMBER: builtins.int
coin: global___BTCCoin.ValueType
@property
def script_configs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BTCScriptConfigWithKeypath]:
@@ -306,6 +307,7 @@ class BTCSignInitRequest(google.protobuf.message.Message):
"""must be <500000000"""

format_unit: global___BTCSignInitRequest.FormatUnit.ValueType
contains_silent_payment_outputs: builtins.bool
def __init__(self,
*,
coin: global___BTCCoin.ValueType = ...,
@@ -315,8 +317,9 @@ class BTCSignInitRequest(google.protobuf.message.Message):
num_outputs: builtins.int = ...,
locktime: builtins.int = ...,
format_unit: global___BTCSignInitRequest.FormatUnit.ValueType = ...,
contains_silent_payment_outputs: builtins.bool = ...,
) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["coin",b"coin","format_unit",b"format_unit","locktime",b"locktime","num_inputs",b"num_inputs","num_outputs",b"num_outputs","script_configs",b"script_configs","version",b"version"]) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["coin",b"coin","contains_silent_payment_outputs",b"contains_silent_payment_outputs","format_unit",b"format_unit","locktime",b"locktime","num_inputs",b"num_inputs","num_outputs",b"num_outputs","script_configs",b"script_configs","version",b"version"]) -> None: ...
global___BTCSignInitRequest = BTCSignInitRequest

class BTCSignNextResponse(google.protobuf.message.Message):
@@ -356,6 +359,8 @@ class BTCSignNextResponse(google.protobuf.message.Message):
SIGNATURE_FIELD_NUMBER: builtins.int
PREV_INDEX_FIELD_NUMBER: builtins.int
ANTI_KLEPTO_SIGNER_COMMITMENT_FIELD_NUMBER: builtins.int
GENERATED_OUTPUT_PKSCRIPT_FIELD_NUMBER: builtins.int
SILENT_PAYMENT_DLEQ_PROOF_FIELD_NUMBER: builtins.int
type: global___BTCSignNextResponse.Type.ValueType
index: builtins.int
"""index of the current input or output"""
@@ -371,6 +376,10 @@ class BTCSignNextResponse(google.protobuf.message.Message):

@property
def anti_klepto_signer_commitment(self) -> antiklepto_pb2.AntiKleptoSignerCommitment: ...
generated_output_pkscript: builtins.bytes
"""Generated output. The host *must* verify its correctness using `silent_payment_dleq_proof`."""

silent_payment_dleq_proof: builtins.bytes
def __init__(self,
*,
type: global___BTCSignNextResponse.Type.ValueType = ...,
@@ -379,9 +388,11 @@ class BTCSignNextResponse(google.protobuf.message.Message):
signature: builtins.bytes = ...,
prev_index: builtins.int = ...,
anti_klepto_signer_commitment: typing.Optional[antiklepto_pb2.AntiKleptoSignerCommitment] = ...,
generated_output_pkscript: builtins.bytes = ...,
silent_payment_dleq_proof: builtins.bytes = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["anti_klepto_signer_commitment",b"anti_klepto_signer_commitment"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["anti_klepto_signer_commitment",b"anti_klepto_signer_commitment","has_signature",b"has_signature","index",b"index","prev_index",b"prev_index","signature",b"signature","type",b"type"]) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["anti_klepto_signer_commitment",b"anti_klepto_signer_commitment","generated_output_pkscript",b"generated_output_pkscript","has_signature",b"has_signature","index",b"index","prev_index",b"prev_index","signature",b"signature","silent_payment_dleq_proof",b"silent_payment_dleq_proof","type",b"type"]) -> None: ...
global___BTCSignNextResponse = BTCSignNextResponse

class BTCSignInputRequest(google.protobuf.message.Message):
@@ -424,13 +435,25 @@ global___BTCSignInputRequest = BTCSignInputRequest

class BTCSignOutputRequest(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
class SilentPayment(google.protobuf.message.Message):
"""https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
ADDRESS_FIELD_NUMBER: builtins.int
address: typing.Text
def __init__(self,
*,
address: typing.Text = ...,
) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["address",b"address"]) -> None: ...

OURS_FIELD_NUMBER: builtins.int
TYPE_FIELD_NUMBER: builtins.int
VALUE_FIELD_NUMBER: builtins.int
PAYLOAD_FIELD_NUMBER: builtins.int
KEYPATH_FIELD_NUMBER: builtins.int
SCRIPT_CONFIG_INDEX_FIELD_NUMBER: builtins.int
PAYMENT_REQUEST_INDEX_FIELD_NUMBER: builtins.int
SILENT_PAYMENT_FIELD_NUMBER: builtins.int
ours: builtins.bool
type: global___BTCOutputType.ValueType
"""if ours is false"""
@@ -449,6 +472,12 @@ class BTCSignOutputRequest(google.protobuf.message.Message):
"""If ours is true. References a script config from BTCSignInitRequest"""

payment_request_index: builtins.int
@property
def silent_payment(self) -> global___BTCSignOutputRequest.SilentPayment:
"""If provided, `type` and `payload` is ignored. The generated output pkScript is returned in
BTCSignNextResponse. `contains_silent_payment_outputs` in the init request must be true.
"""
pass
def __init__(self,
*,
ours: builtins.bool = ...,
@@ -458,9 +487,10 @@ class BTCSignOutputRequest(google.protobuf.message.Message):
keypath: typing.Optional[typing.Iterable[builtins.int]] = ...,
script_config_index: builtins.int = ...,
payment_request_index: typing.Optional[builtins.int] = ...,
silent_payment: typing.Optional[global___BTCSignOutputRequest.SilentPayment] = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["_payment_request_index",b"_payment_request_index","payment_request_index",b"payment_request_index"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["_payment_request_index",b"_payment_request_index","keypath",b"keypath","ours",b"ours","payload",b"payload","payment_request_index",b"payment_request_index","script_config_index",b"script_config_index","type",b"type","value",b"value"]) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["_payment_request_index",b"_payment_request_index","payment_request_index",b"payment_request_index","silent_payment",b"silent_payment"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["_payment_request_index",b"_payment_request_index","keypath",b"keypath","ours",b"ours","payload",b"payload","payment_request_index",b"payment_request_index","script_config_index",b"script_config_index","silent_payment",b"silent_payment","type",b"type","value",b"value"]) -> None: ...
def WhichOneof(self, oneof_group: typing_extensions.Literal["_payment_request_index",b"_payment_request_index"]) -> typing.Optional[typing_extensions.Literal["payment_request_index"]]: ...
global___BTCSignOutputRequest = BTCSignOutputRequest

23 changes: 23 additions & 0 deletions src/keystore.c
Original file line number Diff line number Diff line change
@@ -1000,6 +1000,29 @@ bool keystore_secp256k1_schnorr_bip86_sign(
return secp256k1_schnorrsig_verify(ctx, sig64_out, msg32, 32, &pubkey) == 1;
}

bool keystore_secp256k1_get_private_key(
const uint32_t* keypath,
const size_t keypath_len,
bool tweak_bip86,
uint8_t* key_out)
{
if (tweak_bip86) {
secp256k1_keypair __attribute__((__cleanup__(_cleanup_keypair))) keypair = {0};
secp256k1_xonly_pubkey pubkey = {0};
if (!_schnorr_bip86_keypair(keypath, keypath_len, &keypair, &pubkey)) {
return false;
}
const secp256k1_context* ctx = wally_get_secp_context();
return secp256k1_keypair_sec(ctx, key_out, &keypair) == 1;
}
struct ext_key xprv __attribute__((__cleanup__(keystore_zero_xkey))) = {0};
if (!_get_xprv_twice(keypath, keypath_len, &xprv)) {
return false;
}
memcpy(key_out, xprv.priv_key + 1, 32);
return true;
}

#ifdef TESTING
void keystore_mock_unlocked(const uint8_t* seed, size_t seed_len, const uint8_t* bip39_seed)
{
14 changes: 14 additions & 0 deletions src/keystore.h
Original file line number Diff line number Diff line change
@@ -303,6 +303,20 @@ USE_RESULT bool keystore_secp256k1_schnorr_bip86_sign(
const uint8_t* msg32,
uint8_t* sig64_out);

/**
* Get the private key at the keypath.
*
* @param[in] keypath derivation keypath
* @param[in] keypath_len number of elements in keypath
* @param[in] tweak_bip86 if true, the resulting private key is tweaked with the BIP-86 tweak.
* @param[out] key_out resulting private key, must be 32 bytes.
*/
USE_RESULT bool keystore_secp256k1_get_private_key(
const uint32_t* keypath,
size_t keypath_len,
bool tweak_bip86,
uint8_t* key_out);

#ifdef TESTING
/**
* convenience to mock the keystore state (locked, seed) in tests.
58 changes: 58 additions & 0 deletions src/rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -22,11 +22,13 @@ members = [
"bitbox02",
"bitbox02-sys",
"erc20_params",
"streaming-silent-payments",
]

resolver = "2"

[workspace.dependencies]
bech32 = { version = "0.11.0", default-features = false }
bitcoin = { version = "0.32.2", default-features = false }
hex = { version = "0.4", default-features = false, features = ["alloc"] }
num-bigint = { version = "0.4.3", default-features = false }
6 changes: 4 additions & 2 deletions src/rust/bitbox02-rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ util = { path = "../util" }
erc20_params = { path = "../erc20_params", optional = true }
binascii = { version = "0.1.4", default-features = false, features = ["encode"] }
bitbox02-noise = {path = "../bitbox02-noise"}
streaming-silent-payments = { path = "../streaming-silent-payments", optional = true }
hex = { workspace = true }
sha2 = { workspace = true }
sha3 = { workspace = true, optional = true }
@@ -41,8 +42,8 @@ num-bigint = { workspace = true, optional = true }
num-traits = { version = "0.2", default-features = false }
# If you change this, also change src/rust/.cargo/config.toml.
bip32-ed25519 = { git = "https://github.com/BitBoxSwiss/rust-bip32-ed25519", tag = "v0.2.0", optional = true }
bech32 = { version = "0.11.0", default-features = false, features = ["alloc"], optional = true }
blake2 = { version = "0.10.6", default-features = false, features = ["size_opt"], optional = true }
bech32 = { workspace = true, optional = true }
blake2 = { version = "0.10.6", default-features = false, optional = true }
minicbor = { version = "0.24.0", default-features = false, features = ["alloc"], optional = true }
crc = { version = "3.0.1", optional = true }
ed25519-dalek = { version = "2.1.1", default-features = false, features = ["hazmat", "digest"], optional = true }
@@ -77,6 +78,7 @@ app-ethereum = [
app-bitcoin = [
"dep:bech32",
"dep:miniscript",
"dep:streaming-silent-payments",
"bitbox02/app-bitcoin",
]
app-litecoin = [
241 changes: 197 additions & 44 deletions src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs
Original file line number Diff line number Diff line change
@@ -454,6 +454,8 @@ pub struct BtcSignInitRequest {
pub locktime: u32,
#[prost(enumeration = "btc_sign_init_request::FormatUnit", tag = "8")]
pub format_unit: i32,
#[prost(bool, tag = "9")]
pub contains_silent_payment_outputs: bool,
}
/// Nested message and enum types in `BTCSignInitRequest`.
pub mod btc_sign_init_request {
@@ -517,6 +519,11 @@ pub struct BtcSignNextResponse {
pub anti_klepto_signer_commitment: ::core::option::Option<
AntiKleptoSignerCommitment,
>,
/// Generated output. The host *must* verify its correctness using `silent_payment_dleq_proof`.
#[prost(bytes = "vec", tag = "7")]
pub generated_output_pkscript: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", tag = "8")]
pub silent_payment_dleq_proof: ::prost::alloc::vec::Vec<u8>,
}
/// Nested message and enum types in `BTCSignNextResponse`.
pub mod btc_sign_next_response {
@@ -619,6 +626,20 @@ pub struct BtcSignOutputRequest {
pub script_config_index: u32,
#[prost(uint32, optional, tag = "7")]
pub payment_request_index: ::core::option::Option<u32>,
/// If provided, `type` and `payload` is ignored. The generated output pkScript is returned in
/// BTCSignNextResponse. `contains_silent_payment_outputs` in the init request must be true.
#[prost(message, optional, tag = "8")]
pub silent_payment: ::core::option::Option<btc_sign_output_request::SilentPayment>,
}
/// Nested message and enum types in `BTCSignOutputRequest`.
pub mod btc_sign_output_request {
/// <https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki>
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SilentPayment {
#[prost(string, tag = "1")]
pub address: ::prost::alloc::string::String,
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
3 changes: 3 additions & 0 deletions src/rust/bitbox02-sys/build.rs
Original file line number Diff line number Diff line change
@@ -140,6 +140,7 @@ pub fn main() -> Result<(), &'static str> {
.args(["--allowlist-function", "keystore_lock"])
.args(["--allowlist-function", "keystore_create_and_store_seed"])
.args(["--allowlist-function", "keystore_copy_seed"])
.args(["--allowlist-function", "keystore_secp256k1_get_private_key"])
.args(["--allowlist-function", "keystore_get_bip39_mnemonic"])
.args(["--allowlist-function", "keystore_get_bip39_word"])
.args(["--allowlist-function", "keystore_get_ed25519_seed"])
@@ -224,6 +225,8 @@ pub fn main() -> Result<(), &'static str> {
.args(["--allowlist-function", "wally_hash160"])
.args(["--allowlist-function", "wally_sha512"])
.args(["--allowlist-function", "printf"])
.args(["--allowlist-function", "bitbox_secp256k1_dleq_prove"])
.args(["--allowlist-function", "bitbox_secp256k1_dleq_verify"])
.arg("wrapper.h")
.arg("--")
.arg("-DPB_NO_PACKED_STRUCTS=1")
1 change: 1 addition & 0 deletions src/rust/bitbox02-sys/wrapper.h
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@
#include <reset.h>
#include <screen.h>
#include <sd.h>
#include <secp256k1_ecdsa_adaptor.h>
#include <secp256k1_ecdsa_s2c.h>
#include <securechip/securechip.h>
#include <system.h>
5 changes: 5 additions & 0 deletions src/rust/bitbox02/Cargo.toml
Original file line number Diff line number Diff line change
@@ -25,6 +25,11 @@ license = "Apache-2.0"
bitbox02-sys = {path="../bitbox02-sys"}
util = {path = "../util"}
zeroize = { workspace = true }
bitcoin = { workspace = true }
hex = { workspace = true }

[dev-dependencies]
hex = { workspace = true }

[features]
# Only to be enabled in unit tests.
55 changes: 51 additions & 4 deletions src/rust/bitbox02/src/keystore.rs
Original file line number Diff line number Diff line change
@@ -224,6 +224,24 @@ pub fn encode_xpub_at_keypath(keypath: &[u32]) -> Result<Vec<u8>, ()> {
}
}

pub fn secp256k1_get_private_key(
keypath: &[u32],
tweak_bip86: bool,
) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
let mut key = zeroize::Zeroizing::new(vec![0u8; 32]);
match unsafe {
bitbox02_sys::keystore_secp256k1_get_private_key(
keypath.as_ptr(),
keypath.len() as _,
tweak_bip86,
key.as_mut_ptr(),
)
} {
true => Ok(key),
false => Err(()),
}
}

pub struct SignResult {
pub signature: [u8; 64],
pub recid: u8,
@@ -361,6 +379,7 @@ pub fn secp256k1_schnorr_bip86_pubkey(pubkey33: &[u8]) -> Result<[u8; 32], ()> {
mod tests {
use super::*;
use crate::testing::{mock_unlocked, mock_unlocked_using_mnemonic, TEST_MNEMONIC};
use util::bip32::HARDENED;

#[test]
fn test_bip39_mnemonic_to_seed() {
@@ -490,7 +509,7 @@ mod tests {
"income soft level reunion height pony crane use unfold win keen satisfy",
);
assert_eq!(
bip85_bip39(12, util::bip32::HARDENED - 1).unwrap().as_ref() as &str,
bip85_bip39(12, HARDENED - 1).unwrap().as_ref() as &str,
"carry build nerve market domain energy mistake script puzzle replace mixture idea",
);
assert_eq!(
@@ -505,7 +524,7 @@ mod tests {
// Invalid number of words.
assert!(bip85_bip39(10, 0).is_err());
// Index too high.
assert!(bip85_bip39(12, util::bip32::HARDENED).is_err());
assert!(bip85_bip39(12, HARDENED).is_err());
}

#[test]
@@ -527,11 +546,39 @@ mod tests {
b"\xe7\xd9\xce\x75\xf8\xcb\x17\x57\x0e\x66\x54\x17\xb4\x7f\xa0\xbe",
);
assert_eq!(
bip85_ln(util::bip32::HARDENED - 1).unwrap().as_slice(),
bip85_ln(HARDENED - 1).unwrap().as_slice(),
b"\x1f\x3b\x75\xea\x25\x27\x49\x70\x0a\x1e\x45\x34\x69\x14\x8c\xa6",
);

// Index too high.
assert!(bip85_ln(util::bip32::HARDENED).is_err());
assert!(bip85_ln(HARDENED).is_err());
}

#[test]
fn test_secp256k1_get_private_key() {
lock();
let keypath = &[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0];
assert!(secp256k1_get_private_key(keypath, false).is_err());

mock_unlocked_using_mnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
"",
);

assert_eq!(
hex::encode(secp256k1_get_private_key(keypath, false).unwrap()),
"4604b4b710fe91f584fff084e1a9159fe4f8408fff380596a604948474ce4fa3"
);

// See first test vector in
// https://github.com/bitcoin/bips/blob/edffe529056f6dfd33d8f716fb871467c3c09263/bip-0086.mediawiki#test-vectors
// The below privte key's public key is: a60869f0dbcf1dc659c9cecbaf8050135ea9e8cdc487053f1dc6880949dc684c.
assert_eq!(
hex::encode(
secp256k1_get_private_key(&[86 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0], true)
.unwrap()
),
"eaac016f36e8c18347fbacf05ab7966708fbfce7ce3bf1dc32a09dd0645db038",
);
}
}
91 changes: 90 additions & 1 deletion src/rust/bitbox02/src/secp256k1.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022 Shift Crypto AG
// Copyright 2022-2024 Shift Crypto AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use bitcoin::secp256k1::ffi::CPtr;

use alloc::vec::Vec;

pub fn ecdsa_anti_exfil_host_commit(rand32: &[u8]) -> Result<Vec<u8>, ()> {
@@ -27,3 +29,90 @@ pub fn ecdsa_anti_exfil_host_commit(rand32: &[u8]) -> Result<Vec<u8>, ()> {
_ => Err(()),
}
}

pub fn dleq_prove(
sk: &[u8; 32],
gen2: &bitcoin::secp256k1::PublicKey,
p1: &bitcoin::secp256k1::PublicKey,
p2: &bitcoin::secp256k1::PublicKey,
) -> Result<Vec<u8>, ()> {
let mut s = [0u8; 32];
let mut e = [0u8; 32];
let result = unsafe {
bitbox02_sys::bitbox_secp256k1_dleq_prove(
bitbox02_sys::wally_get_secp_context(),
s.as_mut_ptr(),
e.as_mut_ptr(),
sk.as_ptr(),
gen2.as_c_ptr() as _,
p1.as_c_ptr() as _,
p2.as_c_ptr() as _,
)
};
if result == 1 {
let mut result = s.to_vec();
result.extend(&e);
Ok(result)
} else {
Err(())
}
}

pub fn dleq_verify(
proof: [u8; 64],
gen2: &bitcoin::secp256k1::PublicKey,
p1: &bitcoin::secp256k1::PublicKey,
p2: &bitcoin::secp256k1::PublicKey,
) -> Result<(), ()> {
let result = unsafe {
bitbox02_sys::bitbox_secp256k1_dleq_verify(
bitbox02_sys::wally_get_secp_context(),
proof[..32].as_ptr(),
proof[32..].as_ptr(),
p1.as_c_ptr() as _,
gen2.as_c_ptr() as _,
p2.as_c_ptr() as _,
)
};
if result == 1 {
Ok(())
} else {
Err(())
}
}

#[cfg(test)]
mod tests {
use super::*;
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};

#[test]
fn test_dleq() {
let secp = Secp256k1::new();
let seckey_bytes = b"\x07\x7e\xb7\x5a\x52\xec\xa2\x4c\xde\xdf\x05\x8c\x92\xf1\xca\x8b\x9d\x48\x41\x77\x1f\xd6\xba\xa3\xd2\x78\x85\xfb\x5b\x49\xfb\xa2";
let seckey = SecretKey::from_slice(seckey_bytes).unwrap();

let pubkey = seckey.public_key(&secp);

let other_base_bytes = b"\x03\x89\x14\x0f\x7b\xb8\x52\xf0\x20\xf1\x54\xe5\x59\x08\xfe\x36\x99\xdc\x9f\x65\x15\x3e\x68\x15\x27\xf0\xd5\x5a\xab\xed\x93\x7f\x4b";
let other_base = PublicKey::from_slice(other_base_bytes).unwrap();

let other_pubkey = other_base;
let other_pubkey = other_pubkey.mul_tweak(&secp, &seckey.into()).unwrap();
let proof = dleq_prove(seckey_bytes, &other_base, &pubkey, &other_pubkey).unwrap();
// Check against fixture so potential upstream changes in the DLEQ implementation get
// caught. Incompatible changes can break BitBox client libraries that rely on this
// specific DLEQ implementation.
assert_eq!(
hex::encode(&proof),
"6c885f825f6ce7565bc6d0bfda90506b11e2682dfe943f5a85badf1c8a96edc5f5e03f5ee2c58bf979646fbada920f9f1c5bd92805fb5b01534b42d26a550f79",
);
dleq_verify(
proof.try_into().unwrap(),
&other_base,
&pubkey,
&other_pubkey,
)
.unwrap();
}
}
30 changes: 30 additions & 0 deletions src/rust/streaming-silent-payments/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright 2024 Shift Crypto AG
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

[package]
name = "streaming-silent-payments"
version = "0.1.0"
authors = ["Shift Crypto AG <support@bitbox.swiss>"]
edition = "2021"
license = "Apache-2.0"

[dependencies]
bitcoin = { workspace = true }
bech32 = { workspace = true }
bitbox02 = { path = "../bitbox02" }

[dev-dependencies]
serde = { version = "1.0", features = ["derive"] }
hex = { workspace = true }
serde_json = "1.0"
54 changes: 54 additions & 0 deletions src/rust/streaming-silent-payments/src/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// File copied and adapted from:
// https://github.com/cygnet3/rust-silentpayments/blob/395b153b6d98ea33a59306c1a8a189d4ca152571/src/utils/hash.rs

#![allow(non_snake_case)]

use bitcoin::hashes::{sha256t_hash_newtype, Hash, HashEngine};
use bitcoin::secp256k1::{PublicKey, Scalar};

sha256t_hash_newtype! {
struct InputsTag = hash_str("BIP0352/Inputs");

/// BIP0352-tagged hash with tag \"Inputs\".
///
/// This is used for computing the inputs hash.
#[hash_newtype(forward)]
struct InputsHash(_);

pub(crate) struct SharedSecretTag = hash_str("BIP0352/SharedSecret");

/// BIP0352-tagged hash with tag \"SharedSecret\".
///
/// This hash type is for computing the shared secret.
#[hash_newtype(forward)]
pub(crate) struct SharedSecretHash(_);
}

impl InputsHash {
pub(crate) fn from_outpoint_and_A_sum(
smallest_outpoint: &bitcoin::OutPoint,
A_sum: PublicKey,
) -> InputsHash {
let mut eng = InputsHash::engine();
eng.input(&bitcoin::consensus::serialize(smallest_outpoint));
eng.input(&A_sum.serialize());
InputsHash::from_engine(eng)
}
pub(crate) fn to_scalar(self) -> Scalar {
// This is statistically extremely unlikely to panic.
Scalar::from_be_bytes(self.to_byte_array()).unwrap()
}
}

impl SharedSecretHash {
pub(crate) fn from_ecdh_and_k(ecdh: &PublicKey, k: u32) -> SharedSecretHash {
let mut eng = SharedSecretHash::engine();
eng.input(&ecdh.serialize());
eng.input(&k.to_be_bytes());
SharedSecretHash::from_engine(eng)
}
}

pub(crate) fn calculate_input_hash(outpoint: &bitcoin::OutPoint, A_sum: PublicKey) -> Scalar {
InputsHash::from_outpoint_and_A_sum(outpoint, A_sum).to_scalar()
}
408 changes: 408 additions & 0 deletions src/rust/streaming-silent-payments/src/lib.rs

Large diffs are not rendered by default.

186 changes: 186 additions & 0 deletions src/rust/streaming-silent-payments/tests/table_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright 2024 Shift Crypto AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![allow(non_snake_case)]

use serde::Deserialize;
use std::fs::File;
use std::io::BufReader;
use std::str::FromStr;

use streaming_silent_payments::{
bitcoin,
bitcoin::secp256k1::{SecretKey, XOnlyPublicKey},
InputType, Network, SilentPayment,
};

/// The following structs have been copied from:
/// https://github.com/cygnet3/rust-silentpayments/blob/395b153b6d98ea33a59306c1a8a189d4ca152571/tests/common/structs.rs
#[derive(Debug, Deserialize)]
pub struct TestData {
pub comment: String,
pub sending: Vec<SendingData>,
pub receiving: Vec<ReceivingData>,
}

#[derive(Debug, Deserialize)]
pub struct ReceivingData {
pub given: ReceivingDataGiven,
pub expected: ReceivingDataExpected,
}

#[derive(Debug, Deserialize)]
pub struct ReceivingKeyMaterial {
pub scan_priv_key: String,
pub spend_priv_key: String,
}

#[derive(Debug, Deserialize)]
pub struct HexStr {
pub hex: String,
}

#[derive(Debug, Deserialize)]
pub struct ScriptPubKey {
pub scriptPubKey: HexStr,
}

#[derive(Debug, Deserialize)]
pub struct ReceivingVinData {
pub txid: String,
pub vout: u32,
pub scriptSig: String,
pub txinwitness: String,
pub prevout: ScriptPubKey,
}

#[derive(Debug, Deserialize)]
pub struct ReceivingDataGiven {
pub vin: Vec<ReceivingVinData>,
pub key_material: ReceivingKeyMaterial,
pub labels: Vec<u32>,
pub outputs: Vec<String>,
}

#[derive(Debug, Deserialize)]
pub struct ReceivingDataExpected {
pub addresses: Vec<String>,
pub outputs: Vec<OutputWithSignature>,
}

#[derive(Debug, Deserialize)]
pub struct SendingData {
pub given: SendingDataGiven,
pub expected: SendingDataExpected,
}

#[derive(Debug, Deserialize)]
pub struct SendingDataGiven {
pub vin: Vec<SendingVinData>,
pub recipients: Vec<String>,
}

#[derive(Debug, Deserialize)]
pub struct SendingVinData {
pub txid: String,
pub vout: u32,
pub scriptSig: String,
pub txinwitness: String,
pub prevout: ScriptPubKey,
pub private_key: String,
}

#[derive(Debug, Deserialize)]
pub struct SendingDataExpected {
pub outputs: Vec<Vec<String>>,
}

#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct OutputWithSignature {
pub pub_key: String,
pub priv_key_tweak: String,
pub signature: String,
}

#[test]
fn test_sending() {
let reader =
BufReader::new(File::open("./tests/testdata/send_and_receive_test_vectors.json").unwrap());
let tests: Vec<TestData> = serde_json::from_reader(reader).unwrap();
for (i, test) in tests.iter().enumerate() {
if test.comment == "Single recipient: taproot input with NUMS point" {
// SilentPayment API does not allow passing P2TR script path spends - We only support BIP-86
// Taproot key-path spends.
continue;
}
if test.comment == "P2PKH and P2WPKH Uncompressed Keys are skipped" {
// We don't support uncompressed keys.
continue;
}
if test.comment == "Skip invalid P2SH inputs" {
// SilentPayment API does not allow passing invalid P2SH inputs.
continue;
}

for (j, sending_data) in test.sending.iter().enumerate() {
if sending_data.expected.outputs.len() != 1
|| sending_data.expected.outputs[0].len() != 1
{
println!("Skipping test #{}/{}", i, j);
continue;
}
println!("Running test #{}/{} - {}", i, j, test.comment);

let expected = XOnlyPublicKey::from_str(&sending_data.expected.outputs[0][0]).unwrap();

// One SP recipient results in one output.
assert_eq!(sending_data.given.recipients.len(), 1);
let sp_address = sending_data.given.recipients[0].as_str();

let mut v = SilentPayment::new(Network::Btc);
for inp in sending_data.given.vin.iter() {
let pk_script_hex = inp.prevout.scriptPubKey.hex.as_str();
let pk_script_bytes = hex::decode(pk_script_hex).unwrap();
let pk_script = bitcoin::Script::from_bytes(&pk_script_bytes);

let input_type = if pk_script.is_p2pkh() {
InputType::P2pkh
} else if pk_script.is_p2wpkh() {
InputType::P2wpkh
} else if pk_script.is_p2tr() {
let witness: bitcoin::Witness =
bitcoin::consensus::deserialize(&hex::decode(&inp.txinwitness).unwrap())
.unwrap();
// Regular keypath spend. One test case was skipped above (NUMS) which is a
// script path spend, which we currently don't support.
assert!(witness.len() == 1 && witness.nth(0).unwrap().len() == 64);
InputType::P2trKeypathSpend
} else if pk_script.is_p2sh() {
panic!("tests don't include p2sh - parse for p2sh-p2wpkh if needed")
} else {
panic!("unrecognized input")
};
v.add_input(
input_type,
&SecretKey::from_str(&inp.private_key).unwrap(),
bitcoin::OutPoint::new(bitcoin::Txid::from_str(&inp.txid).unwrap(), inp.vout),
)
.unwrap();
}

assert_eq!(v.create_output(sp_address).unwrap().pubkey, expected);
}
}
}

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/rust/vendor/itoa/.cargo-checksum.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"files":{"Cargo.toml":"4b12156f19f1d3a10516ba0177197325f072340e40de4b6a376732fcef3105b2","LICENSE-APACHE":"62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"48573443063fa4e0786c3b46f42b6efd1f171c6b73408a64afc1b34de89f31fe","benches/bench.rs":"636f3093bd461210ad3063289d455f90669c4a1be3273bcd30898de39f02c641","src/lib.rs":"22f02b06399d4c6849dac5a1b517d3e5736dd33edc3955101ee0be6afc8376eb","src/udiv128.rs":"d28c1872c37ee2185931babcb20a221b8706a5aa8abc4963419763888023ff17","tests/test.rs":"aa1e910573a1d847d39773b4a2e4c597a8d3810070332673df0f6864cab24807"},"package":"49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"}
43 changes: 43 additions & 0 deletions src/rust/vendor/itoa/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.

[package]
edition = "2018"
rust-version = "1.36"
name = "itoa"
version = "1.0.11"
authors = ["David Tolnay <dtolnay@gmail.com>"]
exclude = [
"performance.png",
"chart/**",
]
description = "Fast integer primitive to string conversion"
documentation = "https://docs.rs/itoa"
readme = "README.md"
keywords = ["integer"]
categories = [
"value-formatting",
"no-std",
"no-std::no-alloc",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/dtolnay/itoa"

[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]
targets = ["x86_64-unknown-linux-gnu"]

[lib]
doc-scrape-examples = false

[dependencies.no-panic]
version = "0.1"
optional = true
176 changes: 176 additions & 0 deletions src/rust/vendor/itoa/LICENSE-APACHE
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.

"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:

(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and

(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and

(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and

(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.

You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS
23 changes: 23 additions & 0 deletions src/rust/vendor/itoa/LICENSE-MIT
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
59 changes: 59 additions & 0 deletions src/rust/vendor/itoa/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
itoa
====

[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/itoa-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/itoa)
[<img alt="crates.io" src="https://img.shields.io/crates/v/itoa.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/itoa)
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-itoa-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/itoa)
[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/dtolnay/itoa/ci.yml?branch=master&style=for-the-badge" height="20">](https://github.com/dtolnay/itoa/actions?query=branch%3Amaster)

This crate provides a fast conversion of integer primitives to decimal strings.
The implementation comes straight from [libcore] but avoids the performance
penalty of going through [`core::fmt::Formatter`].

See also [`ryu`] for printing floating point primitives.

*Version requirement: rustc 1.36+*

[libcore]: https://github.com/rust-lang/rust/blob/b8214dc6c6fc20d0a660fb5700dca9ebf51ebe89/src/libcore/fmt/num.rs#L201-L254
[`core::fmt::Formatter`]: https://doc.rust-lang.org/std/fmt/struct.Formatter.html
[`ryu`]: https://github.com/dtolnay/ryu

```toml
[dependencies]
itoa = "1.0"
```

<br>

## Example

```rust
fn main() {
let mut buffer = itoa::Buffer::new();
let printed = buffer.format(128u64);
assert_eq!(printed, "128");
}
```

<br>

## Performance (lower is better)

![performance](https://raw.githubusercontent.com/dtolnay/itoa/master/performance.png)

<br>

#### License

<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
</sup>

<br>

<sub>
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
</sub>
55 changes: 55 additions & 0 deletions src/rust/vendor/itoa/benches/bench.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#![feature(test)]
#![allow(non_snake_case)]
#![allow(clippy::cast_lossless)]

extern crate test;

macro_rules! benches {
($($name:ident($value:expr))*) => {
mod bench_itoa_format {
use test::{Bencher, black_box};

$(
#[bench]
fn $name(b: &mut Bencher) {
let mut buffer = itoa::Buffer::new();

b.iter(|| {
let printed = buffer.format(black_box($value));
black_box(printed);
});
}
)*
}

mod bench_std_fmt {
use std::io::Write;
use test::{Bencher, black_box};

$(
#[bench]
fn $name(b: &mut Bencher) {
let mut buf = Vec::with_capacity(40);

b.iter(|| {
buf.clear();
write!(&mut buf, "{}", black_box($value)).unwrap();
black_box(&buf);
});
}
)*
}
}
}

benches! {
bench_u64_0(0u64)
bench_u64_half(u32::max_value() as u64)
bench_u64_max(u64::max_value())

bench_i16_0(0i16)
bench_i16_min(i16::min_value())

bench_u128_0(0u128)
bench_u128_max(u128::max_value())
}
309 changes: 309 additions & 0 deletions src/rust/vendor/itoa/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
//! [![github]](https://github.com/dtolnay/itoa)&ensp;[![crates-io]](https://crates.io/crates/itoa)&ensp;[![docs-rs]](https://docs.rs/itoa)
//!
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
//!
//! <br>
//!
//! This crate provides a fast conversion of integer primitives to decimal
//! strings. The implementation comes straight from [libcore] but avoids the
//! performance penalty of going through [`core::fmt::Formatter`].
//!
//! See also [`ryu`] for printing floating point primitives.
//!
//! [libcore]: https://github.com/rust-lang/rust/blob/b8214dc6c6fc20d0a660fb5700dca9ebf51ebe89/src/libcore/fmt/num.rs#L201-L254
//! [`core::fmt::Formatter`]: https://doc.rust-lang.org/std/fmt/struct.Formatter.html
//! [`ryu`]: https://github.com/dtolnay/ryu
//!
//! # Example
//!
//! ```
//! fn main() {
//! let mut buffer = itoa::Buffer::new();
//! let printed = buffer.format(128u64);
//! assert_eq!(printed, "128");
//! }
//! ```
//!
//! # Performance (lower is better)
//!
//! ![performance](https://raw.githubusercontent.com/dtolnay/itoa/master/performance.png)
#![doc(html_root_url = "https://docs.rs/itoa/1.0.11")]
#![no_std]
#![allow(
clippy::cast_lossless,
clippy::cast_possible_truncation,
clippy::expl_impl_clone_on_copy,
clippy::must_use_candidate,
clippy::needless_doctest_main,
clippy::unreadable_literal
)]

mod udiv128;

use core::mem::{self, MaybeUninit};
use core::{ptr, slice, str};
#[cfg(feature = "no-panic")]
use no_panic::no_panic;

/// A correctly sized stack allocation for the formatted integer to be written
/// into.
///
/// # Example
///
/// ```
/// let mut buffer = itoa::Buffer::new();
/// let printed = buffer.format(1234);
/// assert_eq!(printed, "1234");
/// ```
pub struct Buffer {
bytes: [MaybeUninit<u8>; I128_MAX_LEN],
}

impl Default for Buffer {
#[inline]
fn default() -> Buffer {
Buffer::new()
}
}

impl Copy for Buffer {}

impl Clone for Buffer {
#[inline]
#[allow(clippy::non_canonical_clone_impl)] // false positive https://github.com/rust-lang/rust-clippy/issues/11072
fn clone(&self) -> Self {
Buffer::new()
}
}

impl Buffer {
/// This is a cheap operation; you don't need to worry about reusing buffers
/// for efficiency.
#[inline]
#[cfg_attr(feature = "no-panic", no_panic)]
pub fn new() -> Buffer {
let bytes = [MaybeUninit::<u8>::uninit(); I128_MAX_LEN];
Buffer { bytes }
}

/// Print an integer into this buffer and return a reference to its string
/// representation within the buffer.
#[cfg_attr(feature = "no-panic", no_panic)]
pub fn format<I: Integer>(&mut self, i: I) -> &str {
i.write(unsafe {
&mut *(&mut self.bytes as *mut [MaybeUninit<u8>; I128_MAX_LEN]
as *mut <I as private::Sealed>::Buffer)
})
}
}

/// An integer that can be written into an [`itoa::Buffer`][Buffer].
///
/// This trait is sealed and cannot be implemented for types outside of itoa.
pub trait Integer: private::Sealed {}

// Seal to prevent downstream implementations of the Integer trait.
mod private {
pub trait Sealed: Copy {
type Buffer: 'static;
fn write(self, buf: &mut Self::Buffer) -> &str;
}
}

const DEC_DIGITS_LUT: &[u8] = b"\
0001020304050607080910111213141516171819\
2021222324252627282930313233343536373839\
4041424344454647484950515253545556575859\
6061626364656667686970717273747576777879\
8081828384858687888990919293949596979899";

// Adaptation of the original implementation at
// https://github.com/rust-lang/rust/blob/b8214dc6c6fc20d0a660fb5700dca9ebf51ebe89/src/libcore/fmt/num.rs#L188-L266
macro_rules! impl_Integer {
($($max_len:expr => $t:ident),* as $conv_fn:ident) => {$(
impl Integer for $t {}

impl private::Sealed for $t {
type Buffer = [MaybeUninit<u8>; $max_len];

#[allow(unused_comparisons)]
#[inline]
#[cfg_attr(feature = "no-panic", no_panic)]
fn write(self, buf: &mut [MaybeUninit<u8>; $max_len]) -> &str {
let is_nonnegative = self >= 0;
let mut n = if is_nonnegative {
self as $conv_fn
} else {
// Convert negative number to positive by summing 1 to its two's complement.
(!(self as $conv_fn)).wrapping_add(1)
};
let mut curr = buf.len() as isize;
let buf_ptr = buf.as_mut_ptr() as *mut u8;
let lut_ptr = DEC_DIGITS_LUT.as_ptr();

// Need at least 16 bits for the 4-digits-at-a-time to work.
if mem::size_of::<$t>() >= 2 {
// Eagerly decode 4 digits at a time.
while n >= 10000 {
let rem = (n % 10000) as isize;
n /= 10000;

let d1 = (rem / 100) << 1;
let d2 = (rem % 100) << 1;
curr -= 4;
unsafe {
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2);
}
}
}

// If we reach here, numbers are <=9999 so at most 4 digits long.
let mut n = n as isize; // Possibly reduce 64-bit math.

// Decode 2 more digits, if >2 digits.
if n >= 100 {
let d1 = (n % 100) << 1;
n /= 100;
curr -= 2;
unsafe {
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
}
}

// Decode last 1 or 2 digits.
if n < 10 {
curr -= 1;
unsafe {
*buf_ptr.offset(curr) = (n as u8) + b'0';
}
} else {
let d1 = n << 1;
curr -= 2;
unsafe {
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
}
}

if !is_nonnegative {
curr -= 1;
unsafe {
*buf_ptr.offset(curr) = b'-';
}
}

let len = buf.len() - curr as usize;
let bytes = unsafe { slice::from_raw_parts(buf_ptr.offset(curr), len) };
unsafe { str::from_utf8_unchecked(bytes) }
}
}
)*};
}

const I8_MAX_LEN: usize = 4;
const U8_MAX_LEN: usize = 3;
const I16_MAX_LEN: usize = 6;
const U16_MAX_LEN: usize = 5;
const I32_MAX_LEN: usize = 11;
const U32_MAX_LEN: usize = 10;
const I64_MAX_LEN: usize = 20;
const U64_MAX_LEN: usize = 20;

impl_Integer!(
I8_MAX_LEN => i8,
U8_MAX_LEN => u8,
I16_MAX_LEN => i16,
U16_MAX_LEN => u16,
I32_MAX_LEN => i32,
U32_MAX_LEN => u32
as u32);

impl_Integer!(I64_MAX_LEN => i64, U64_MAX_LEN => u64 as u64);

#[cfg(target_pointer_width = "16")]
impl_Integer!(I16_MAX_LEN => isize, U16_MAX_LEN => usize as u16);

#[cfg(target_pointer_width = "32")]
impl_Integer!(I32_MAX_LEN => isize, U32_MAX_LEN => usize as u32);

#[cfg(target_pointer_width = "64")]
impl_Integer!(I64_MAX_LEN => isize, U64_MAX_LEN => usize as u64);

macro_rules! impl_Integer128 {
($($max_len:expr => $t:ident),*) => {$(
impl Integer for $t {}

impl private::Sealed for $t {
type Buffer = [MaybeUninit<u8>; $max_len];

#[allow(unused_comparisons)]
#[inline]
#[cfg_attr(feature = "no-panic", no_panic)]
fn write(self, buf: &mut [MaybeUninit<u8>; $max_len]) -> &str {
let is_nonnegative = self >= 0;
let n = if is_nonnegative {
self as u128
} else {
// Convert negative number to positive by summing 1 to its two's complement.
(!(self as u128)).wrapping_add(1)
};
let mut curr = buf.len() as isize;
let buf_ptr = buf.as_mut_ptr() as *mut u8;

// Divide by 10^19 which is the highest power less than 2^64.
let (n, rem) = udiv128::udivmod_1e19(n);
let buf1 = unsafe { buf_ptr.offset(curr - U64_MAX_LEN as isize) as *mut [MaybeUninit<u8>; U64_MAX_LEN] };
curr -= rem.write(unsafe { &mut *buf1 }).len() as isize;

if n != 0 {
// Memset the base10 leading zeros of rem.
let target = buf.len() as isize - 19;
unsafe {
ptr::write_bytes(buf_ptr.offset(target), b'0', (curr - target) as usize);
}
curr = target;

// Divide by 10^19 again.
let (n, rem) = udiv128::udivmod_1e19(n);
let buf2 = unsafe { buf_ptr.offset(curr - U64_MAX_LEN as isize) as *mut [MaybeUninit<u8>; U64_MAX_LEN] };
curr -= rem.write(unsafe { &mut *buf2 }).len() as isize;

if n != 0 {
// Memset the leading zeros.
let target = buf.len() as isize - 38;
unsafe {
ptr::write_bytes(buf_ptr.offset(target), b'0', (curr - target) as usize);
}
curr = target;

// There is at most one digit left
// because u128::MAX / 10^19 / 10^19 is 3.
curr -= 1;
unsafe {
*buf_ptr.offset(curr) = (n as u8) + b'0';
}
}
}

if !is_nonnegative {
curr -= 1;
unsafe {
*buf_ptr.offset(curr) = b'-';
}
}

let len = buf.len() - curr as usize;
let bytes = unsafe { slice::from_raw_parts(buf_ptr.offset(curr), len) };
unsafe { str::from_utf8_unchecked(bytes) }
}
}
)*};
}

const U128_MAX_LEN: usize = 39;
const I128_MAX_LEN: usize = 40;

impl_Integer128!(I128_MAX_LEN => i128, U128_MAX_LEN => u128);
48 changes: 48 additions & 0 deletions src/rust/vendor/itoa/src/udiv128.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#[cfg(feature = "no-panic")]
use no_panic::no_panic;

/// Multiply unsigned 128 bit integers, return upper 128 bits of the result
#[inline]
#[cfg_attr(feature = "no-panic", no_panic)]
fn u128_mulhi(x: u128, y: u128) -> u128 {
let x_lo = x as u64;
let x_hi = (x >> 64) as u64;
let y_lo = y as u64;
let y_hi = (y >> 64) as u64;

// handle possibility of overflow
let carry = (x_lo as u128 * y_lo as u128) >> 64;
let m = x_lo as u128 * y_hi as u128 + carry;
let high1 = m >> 64;

let m_lo = m as u64;
let high2 = (x_hi as u128 * y_lo as u128 + m_lo as u128) >> 64;

x_hi as u128 * y_hi as u128 + high1 + high2
}

/// Divide `n` by 1e19 and return quotient and remainder
///
/// Integer division algorithm is based on the following paper:
///
/// T. Granlund and P. Montgomery, “Division by Invariant Integers Using Multiplication”
/// in Proc. of the SIGPLAN94 Conference on Programming Language Design and
/// Implementation, 1994, pp. 61–72
///
#[inline]
#[cfg_attr(feature = "no-panic", no_panic)]
pub fn udivmod_1e19(n: u128) -> (u128, u64) {
let d = 10_000_000_000_000_000_000_u64; // 10^19

let quot = if n < 1 << 83 {
((n >> 19) as u64 / (d >> 19)) as u128
} else {
u128_mulhi(n, 156927543384667019095894735580191660403) >> 62
};

let rem = (n - quot * d as u128) as u64;
debug_assert_eq!(quot, n / d as u128);
debug_assert_eq!(rem as u128, n % d as u128);

(quot, rem)
}
30 changes: 30 additions & 0 deletions src/rust/vendor/itoa/tests/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#![allow(non_snake_case)]
#![allow(clippy::cast_lossless)]

macro_rules! test {
($($name:ident($value:expr, $expected:expr))*) => {
$(
#[test]
fn $name() {
let mut buffer = itoa::Buffer::new();
let s = buffer.format($value);
assert_eq!(s, $expected);
}
)*
}
}

test! {
test_u64_0(0u64, "0")
test_u64_half(u32::max_value() as u64, "4294967295")
test_u64_max(u64::max_value(), "18446744073709551615")
test_i64_min(i64::min_value(), "-9223372036854775808")

test_i16_0(0i16, "0")
test_i16_min(i16::min_value(), "-32768")

test_u128_0(0u128, "0")
test_u128_max(u128::max_value(), "340282366920938463463374607431768211455")
test_i128_min(i128::min_value(), "-170141183460469231731687303715884105728")
test_i128_max(i128::max_value(), "170141183460469231731687303715884105727")
}
1 change: 1 addition & 0 deletions src/rust/vendor/ryu/.cargo-checksum.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"files":{"Cargo.lock":"2154f7e5f06489f3b42120f2165661fd552aa03b3d1f4f0cfeb380acad362b22","Cargo.toml":"708b4546804f6c500c45fd048921400f4328fe8f7159e0491faa4a64e38a2b0f","LICENSE-APACHE":"62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a","LICENSE-BOOST":"c9bff75738922193e67fa726fa225535870d2aa1059f91452c411736284ad566","README.md":"86f0a92cf076f4983f99926607ea272c9650a5996fa3921fc5ca5abceb0f18db","benches/bench.rs":"703521c8cb9c6959ee305776a9971d24754b6fff5c1737741be04f956a3692e8","examples/upstream_benchmark.rs":"f702d3598a8fac59134a8058ebf74ba90163b1f23ebbd6c5978a7bd8a888d357","src/buffer/mod.rs":"e32f3fa7e994ff704796e58e115c5258e94a79a184d1608864772f2f2f5274fc","src/common.rs":"cae347e97fc30c50a964f80425e8c3e69ece2b8ab81f9b81b9baa7fcec64a001","src/d2s.rs":"f2612785ebe510c935b979dc5f66f6b8c818ca8a4cf0364ce1fe1d41fea39592","src/d2s_full_table.rs":"9b0186acbc6d65dc55c17e16125be707a2bfb920d22b35d33234b4cc38566a36","src/d2s_intrinsics.rs":"bbf15472f4299942312e80a992cbc2f47f85f17ed193f24084534434dbfb26e7","src/d2s_small_table.rs":"db3bbe4002d816785b0ee233c330f19fa7002f31dab47dc6f67b266996fe3ae4","src/digit_table.rs":"02351ca54cb8cb3679f635115dd094f32fd91750e9f66103c1ee9ec3db507072","src/f2s.rs":"cb96f61d8c6c6c941803a7b629f2bf835e1a20ad9d3e5d3454a30ed3391c3515","src/f2s_intrinsics.rs":"97bab98093838e30c60f5135f54f5ccb039ff7d9f35553ac8e74437743ca47e2","src/lib.rs":"8020040eaf88b50ffe542691d4c5a63e9f07e8ae6893ab433ef86762655dc941","src/parse.rs":"7f8aa7e007caf5dcb03abdc4238157724bb742d0823a3b8a01646fa1f1129154","src/pretty/exponent.rs":"fa914ec63b3f86cbdaf7933d7c44e1bc1f93c1239a29a5f86934680a7e957570","src/pretty/mantissa.rs":"40cb00efe1c3fab559ab58389bd519d556548aa18fb261a90dd48138911d039b","src/pretty/mod.rs":"eb0a8c78019f55a1767943821340e8b1278455e0d88bb4f63f4bd3dde340e387","src/s2d.rs":"c804518a771654e3786bde2b776c56e94e198ce6d3fe1e4e5e2f2a9cb9e607e3","src/s2f.rs":"11d528931ce1a01a93f39efb3fe99fdc3041b41fefafb2efd6a338d2a12b628c","tests/common_test.rs":"599781a637d9b9756858aabfe5c38a0734a550debd3d94774f33792b7b3c8240","tests/d2s_intrinsics_test.rs":"15d11b70810bf04f33f8b185bf7f010a436a4edb47fa4648b1a036568c2c5d15","tests/d2s_table_test.rs":"819c39cc94e3462138d3be337d06e7334de126642d34bf1394e03d2df9c0c90c","tests/d2s_test.rs":"d72aaf37c76a4042ecc12b7d6faf844696016bb72bb20d142ecab3bd6c87e29f","tests/exhaustive.rs":"f475ed9008a2cd86ce95abb577a4b01e9fed23fc16f7e217ccffb3b834005fa0","tests/f2s_test.rs":"ad9e6fe46e712c488b876428c144c79bdff0349b41c57eee5506fc3c9c156624","tests/macros/mod.rs":"8e90a674b3960f9516cb38f4eea0e0981ff902c3b33572ebdb6c5528d3ffa72c","tests/s2d_test.rs":"75c3a1044881718db65e05f25c9f6e1d005392dddb2e8dafb799668bb6a9a5c3","tests/s2f_test.rs":"1ec06646cb65229bfe866ec913901a0d8d736668f30b812fc4b00136a43f5142"},"package":"f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"}
149 changes: 149 additions & 0 deletions src/rust/vendor/ryu/Cargo.lock
103 changes: 103 additions & 0 deletions src/rust/vendor/ryu/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.

[package]
edition = "2018"
rust-version = "1.36"
name = "ryu"
version = "1.0.18"
authors = ["David Tolnay <dtolnay@gmail.com>"]
build = false
exclude = [
"build.rs",
"performance.png",
"chart/**",
]
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Fast floating point to string conversion"
documentation = "https://docs.rs/ryu"
readme = "README.md"
keywords = ["float"]
categories = [
"value-formatting",
"no-std",
"no-std::no-alloc",
]
license = "Apache-2.0 OR BSL-1.0"
repository = "https://github.com/dtolnay/ryu"

[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]
targets = ["x86_64-unknown-linux-gnu"]

[lib]
name = "ryu"
path = "src/lib.rs"
doc-scrape-examples = false

[[example]]
name = "upstream_benchmark"
path = "examples/upstream_benchmark.rs"

[[test]]
name = "s2f_test"
path = "tests/s2f_test.rs"

[[test]]
name = "common_test"
path = "tests/common_test.rs"

[[test]]
name = "s2d_test"
path = "tests/s2d_test.rs"

[[test]]
name = "d2s_test"
path = "tests/d2s_test.rs"

[[test]]
name = "f2s_test"
path = "tests/f2s_test.rs"

[[test]]
name = "d2s_table_test"
path = "tests/d2s_table_test.rs"

[[test]]
name = "exhaustive"
path = "tests/exhaustive.rs"

[[test]]
name = "d2s_intrinsics_test"
path = "tests/d2s_intrinsics_test.rs"

[[bench]]
name = "bench"
path = "benches/bench.rs"

[dependencies.no-panic]
version = "0.1"
optional = true

[dev-dependencies.num_cpus]
version = "1.8"

[dev-dependencies.rand]
version = "0.8"

[dev-dependencies.rand_xorshift]
version = "0.3"

[features]
small = []
176 changes: 176 additions & 0 deletions src/rust/vendor/ryu/LICENSE-APACHE
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.

"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:

(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and

(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and

(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and

(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.

You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS
23 changes: 23 additions & 0 deletions src/rust/vendor/ryu/LICENSE-BOOST
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:

The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
110 changes: 110 additions & 0 deletions src/rust/vendor/ryu/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Ryū

[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/ryu-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/ryu)
[<img alt="crates.io" src="https://img.shields.io/crates/v/ryu.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/ryu)
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-ryu-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/ryu)
[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/dtolnay/ryu/ci.yml?branch=master&style=for-the-badge" height="20">](https://github.com/dtolnay/ryu/actions?query=branch%3Amaster)

Pure Rust implementation of Ryū, an algorithm to quickly convert floating point
numbers to decimal strings.

The PLDI'18 paper [*Ryū: fast float-to-string conversion*][paper] by Ulf Adams
includes a complete correctness proof of the algorithm. The paper is available
under the creative commons CC-BY-SA license.

This Rust implementation is a line-by-line port of Ulf Adams' implementation in
C, [https://github.com/ulfjack/ryu][upstream].

*Requirements: this crate supports any compiler version back to rustc 1.36; it
uses nothing from the Rust standard library so is usable from no_std crates.*

[paper]: https://dl.acm.org/citation.cfm?id=3192369
[upstream]: https://github.com/ulfjack/ryu/tree/77e767f5e056bab96e895072fc21618ecff2f44b

```toml
[dependencies]
ryu = "1.0"
```

<br>

## Example

```rust
fn main() {
let mut buffer = ryu::Buffer::new();
let printed = buffer.format(1.234);
assert_eq!(printed, "1.234");
}
```

<br>

## Performance (lower is better)

![performance](https://raw.githubusercontent.com/dtolnay/ryu/master/performance.png)

You can run upstream's benchmarks with:

```console
$ git clone https://github.com/ulfjack/ryu c-ryu
$ cd c-ryu
$ bazel run -c opt //ryu/benchmark:ryu_benchmark
```

And the same benchmark against our implementation with:

```console
$ git clone https://github.com/dtolnay/ryu rust-ryu
$ cd rust-ryu
$ cargo run --example upstream_benchmark --release
```

These benchmarks measure the average time to print a 32-bit float and average
time to print a 64-bit float, where the inputs are distributed as uniform random
bit patterns 32 and 64 bits wide.

The upstream C code, the unsafe direct Rust port, and the safe pretty Rust API
all perform the same, taking around 21 nanoseconds to format a 32-bit float and
31 nanoseconds to format a 64-bit float.

There is also a Rust-specific benchmark comparing this implementation to the
standard library which you can run with:

```console
$ cargo bench
```

The benchmark shows Ryū approximately 2-5x faster than the standard library
across a range of f32 and f64 inputs. Measurements are in nanoseconds per
iteration; smaller is better.

## Formatting

This library tends to produce more human-readable output than the standard
library's to\_string, which never uses scientific notation. Here are two
examples:

- *ryu:* 1.23e40, *std:* 12300000000000000000000000000000000000000
- *ryu:* 1.23e-40, *std:* 0.000000000000000000000000000000000000000123

Both libraries print short decimals such as 0.0000123 without scientific
notation.

<br>

#### License

<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-BOOST">Boost Software License 1.0</a> at your
option.
</sup>

<br>

<sub>
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
</sub>
62 changes: 62 additions & 0 deletions src/rust/vendor/ryu/benches/bench.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// cargo bench

#![feature(test)]
#![allow(
clippy::approx_constant,
clippy::excessive_precision,
clippy::unreadable_literal
)]

extern crate test;

use std::io::Write;
use std::{f32, f64};
use test::{black_box, Bencher};

macro_rules! benches {
($($name:ident($value:expr),)*) => {
mod bench_ryu {
use super::*;
$(
#[bench]
fn $name(b: &mut Bencher) {
let mut buf = ryu::Buffer::new();

b.iter(move || {
let value = black_box($value);
let formatted = buf.format_finite(value);
black_box(formatted);
});
}
)*
}

mod bench_std_fmt {
use super::*;
$(
#[bench]
fn $name(b: &mut Bencher) {
let mut buf = Vec::with_capacity(20);

b.iter(|| {
buf.clear();
let value = black_box($value);
write!(&mut buf, "{}", value).unwrap();
black_box(buf.as_slice());
});
}
)*
}
};
}

benches! {
bench_0_f64(0f64),
bench_short_f64(0.1234f64),
bench_e_f64(2.718281828459045f64),
bench_max_f64(f64::MAX),
bench_0_f32(0f32),
bench_short_f32(0.1234f32),
bench_e_f32(2.718281828459045f32),
bench_max_f32(f32::MAX),
}
85 changes: 85 additions & 0 deletions src/rust/vendor/ryu/examples/upstream_benchmark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// cargo run --example upstream_benchmark --release

use rand::{Rng, SeedableRng};

const SAMPLES: usize = 10000;
const ITERATIONS: usize = 1000;

struct MeanAndVariance {
n: i64,
mean: f64,
m2: f64,
}

impl MeanAndVariance {
fn new() -> Self {
MeanAndVariance {
n: 0,
mean: 0.0,
m2: 0.0,
}
}

fn update(&mut self, x: f64) {
self.n += 1;
let d = x - self.mean;
self.mean += d / self.n as f64;
let d2 = x - self.mean;
self.m2 += d * d2;
}

fn variance(&self) -> f64 {
self.m2 / (self.n - 1) as f64
}

fn stddev(&self) -> f64 {
self.variance().sqrt()
}
}

macro_rules! benchmark {
($name:ident, $ty:ident) => {
fn $name() -> usize {
let mut rng = rand_xorshift::XorShiftRng::from_seed([123u8; 16]);
let mut mv = MeanAndVariance::new();
let mut throwaway = 0;
for _ in 0..SAMPLES {
let f = loop {
let f = $ty::from_bits(rng.gen());
if f.is_finite() {
break f;
}
};

let t1 = std::time::SystemTime::now();
for _ in 0..ITERATIONS {
throwaway += ryu::Buffer::new().format_finite(f).len();
}
let duration = t1.elapsed().unwrap();
let nanos = duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64;
mv.update(nanos as f64 / ITERATIONS as f64);
}
println!(
"{:12} {:8.3} {:8.3}",
concat!(stringify!($name), ":"),
mv.mean,
mv.stddev(),
);
throwaway
}
};
}

benchmark!(pretty32, f32);
benchmark!(pretty64, f64);

fn main() {
println!("{:>20}{:>9}", "Average", "Stddev");
let mut throwaway = 0;
throwaway += pretty32();
throwaway += pretty64();
if std::env::var_os("ryu-benchmark").is_some() {
// Prevent the compiler from optimizing the code away.
println!("{}", throwaway);
}
}
171 changes: 171 additions & 0 deletions src/rust/vendor/ryu/src/buffer/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
use crate::raw;
use core::mem::MaybeUninit;
use core::{slice, str};
#[cfg(feature = "no-panic")]
use no_panic::no_panic;

const NAN: &str = "NaN";
const INFINITY: &str = "inf";
const NEG_INFINITY: &str = "-inf";

/// Safe API for formatting floating point numbers to text.
///
/// ## Example
///
/// ```
/// let mut buffer = ryu::Buffer::new();
/// let printed = buffer.format_finite(1.234);
/// assert_eq!(printed, "1.234");
/// ```
pub struct Buffer {
bytes: [MaybeUninit<u8>; 24],
}

impl Buffer {
/// This is a cheap operation; you don't need to worry about reusing buffers
/// for efficiency.
#[inline]
#[cfg_attr(feature = "no-panic", no_panic)]
pub fn new() -> Self {
let bytes = [MaybeUninit::<u8>::uninit(); 24];
Buffer { bytes }
}

/// Print a floating point number into this buffer and return a reference to
/// its string representation within the buffer.
///
/// # Special cases
///
/// This function formats NaN as the string "NaN", positive infinity as
/// "inf", and negative infinity as "-inf" to match std::fmt.
///
/// If your input is known to be finite, you may get better performance by
/// calling the `format_finite` method instead of `format` to avoid the
/// checks for special cases.
#[cfg_attr(feature = "no-panic", inline)]
#[cfg_attr(feature = "no-panic", no_panic)]
pub fn format<F: Float>(&mut self, f: F) -> &str {
if f.is_nonfinite() {
f.format_nonfinite()
} else {
self.format_finite(f)
}
}

/// Print a floating point number into this buffer and return a reference to
/// its string representation within the buffer.
///
/// # Special cases
///
/// This function **does not** check for NaN or infinity. If the input
/// number is not a finite float, the printed representation will be some
/// correctly formatted but unspecified numerical value.
///
/// Please check [`is_finite`] yourself before calling this function, or
/// check [`is_nan`] and [`is_infinite`] and handle those cases yourself.
///
/// [`is_finite`]: https://doc.rust-lang.org/std/primitive.f64.html#method.is_finite
/// [`is_nan`]: https://doc.rust-lang.org/std/primitive.f64.html#method.is_nan
/// [`is_infinite`]: https://doc.rust-lang.org/std/primitive.f64.html#method.is_infinite
#[inline]
#[cfg_attr(feature = "no-panic", no_panic)]
pub fn format_finite<F: Float>(&mut self, f: F) -> &str {
unsafe {
let n = f.write_to_ryu_buffer(self.bytes.as_mut_ptr() as *mut u8);
debug_assert!(n <= self.bytes.len());
let slice = slice::from_raw_parts(self.bytes.as_ptr() as *const u8, n);
str::from_utf8_unchecked(slice)
}
}
}

impl Copy for Buffer {}

impl Clone for Buffer {
#[inline]
#[allow(clippy::non_canonical_clone_impl)] // false positive https://github.com/rust-lang/rust-clippy/issues/11072
fn clone(&self) -> Self {
Buffer::new()
}
}

impl Default for Buffer {
#[inline]
#[cfg_attr(feature = "no-panic", no_panic)]
fn default() -> Self {
Buffer::new()
}
}

/// A floating point number, f32 or f64, that can be written into a
/// [`ryu::Buffer`][Buffer].
///
/// This trait is sealed and cannot be implemented for types outside of the
/// `ryu` crate.
pub trait Float: Sealed {}
impl Float for f32 {}
impl Float for f64 {}

pub trait Sealed: Copy {
fn is_nonfinite(self) -> bool;
fn format_nonfinite(self) -> &'static str;
unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize;
}

impl Sealed for f32 {
#[inline]
fn is_nonfinite(self) -> bool {
const EXP_MASK: u32 = 0x7f800000;
let bits = self.to_bits();
bits & EXP_MASK == EXP_MASK
}

#[cold]
#[cfg_attr(feature = "no-panic", inline)]
fn format_nonfinite(self) -> &'static str {
const MANTISSA_MASK: u32 = 0x007fffff;
const SIGN_MASK: u32 = 0x80000000;
let bits = self.to_bits();
if bits & MANTISSA_MASK != 0 {
NAN
} else if bits & SIGN_MASK != 0 {
NEG_INFINITY
} else {
INFINITY
}
}

#[inline]
unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize {
raw::format32(self, result)
}
}

impl Sealed for f64 {
#[inline]
fn is_nonfinite(self) -> bool {
const EXP_MASK: u64 = 0x7ff0000000000000;
let bits = self.to_bits();
bits & EXP_MASK == EXP_MASK
}

#[cold]
#[cfg_attr(feature = "no-panic", inline)]
fn format_nonfinite(self) -> &'static str {
const MANTISSA_MASK: u64 = 0x000fffffffffffff;
const SIGN_MASK: u64 = 0x8000000000000000;
let bits = self.to_bits();
if bits & MANTISSA_MASK != 0 {
NAN
} else if bits & SIGN_MASK != 0 {
NEG_INFINITY
} else {
INFINITY
}
}

#[inline]
unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize {
raw::format64(self, result)
}
}
95 changes: 95 additions & 0 deletions src/rust/vendor/ryu/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Translated from C to Rust. The original C code can be found at
// https://github.com/ulfjack/ryu and carries the following license:
//
// Copyright 2018 Ulf Adams
//
// The contents of this file may be used under the terms of the Apache License,
// Version 2.0.
//
// (See accompanying file LICENSE-Apache or copy at
// http://www.apache.org/licenses/LICENSE-2.0)
//
// Alternatively, the contents of this file may be used under the terms of
// the Boost Software License, Version 1.0.
// (See accompanying file LICENSE-Boost or copy at
// https://www.boost.org/LICENSE_1_0.txt)
//
// Unless required by applicable law or agreed to in writing, this software
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.

// Returns the number of decimal digits in v, which must not contain more than 9
// digits.
#[cfg_attr(feature = "no-panic", inline)]
pub fn decimal_length9(v: u32) -> u32 {
// Function precondition: v is not a 10-digit number.
// (f2s: 9 digits are sufficient for round-tripping.)
debug_assert!(v < 1000000000);

if v >= 100000000 {
9
} else if v >= 10000000 {
8
} else if v >= 1000000 {
7
} else if v >= 100000 {
6
} else if v >= 10000 {
5
} else if v >= 1000 {
4
} else if v >= 100 {
3
} else if v >= 10 {
2
} else {
1
}
}

// Returns e == 0 ? 1 : [log_2(5^e)]; requires 0 <= e <= 3528.
#[cfg_attr(feature = "no-panic", inline)]
#[allow(dead_code)]
pub fn log2_pow5(e: i32) -> i32 /* or u32 -> u32 */ {
// This approximation works up to the point that the multiplication
// overflows at e = 3529. If the multiplication were done in 64 bits, it
// would fail at 5^4004 which is just greater than 2^9297.
debug_assert!(e >= 0);
debug_assert!(e <= 3528);
((e as u32 * 1217359) >> 19) as i32
}

// Returns e == 0 ? 1 : ceil(log_2(5^e)); requires 0 <= e <= 3528.
#[cfg_attr(feature = "no-panic", inline)]
pub fn pow5bits(e: i32) -> i32 /* or u32 -> u32 */ {
// This approximation works up to the point that the multiplication
// overflows at e = 3529. If the multiplication were done in 64 bits, it
// would fail at 5^4004 which is just greater than 2^9297.
debug_assert!(e >= 0);
debug_assert!(e <= 3528);
(((e as u32 * 1217359) >> 19) + 1) as i32
}

#[cfg_attr(feature = "no-panic", inline)]
#[allow(dead_code)]
pub fn ceil_log2_pow5(e: i32) -> i32 /* or u32 -> u32 */ {
log2_pow5(e) + 1
}

// Returns floor(log_10(2^e)); requires 0 <= e <= 1650.
#[cfg_attr(feature = "no-panic", inline)]
pub fn log10_pow2(e: i32) -> u32 /* or u32 -> u32 */ {
// The first value this approximation fails for is 2^1651 which is just greater than 10^297.
debug_assert!(e >= 0);
debug_assert!(e <= 1650);
(e as u32 * 78913) >> 18
}

// Returns floor(log_10(5^e)); requires 0 <= e <= 2620.
#[cfg_attr(feature = "no-panic", inline)]
pub fn log10_pow5(e: i32) -> u32 /* or u32 -> u32 */ {
// The first value this approximation fails for is 5^2621 which is just greater than 10^1832.
debug_assert!(e >= 0);
debug_assert!(e <= 2620);
(e as u32 * 732923) >> 20
}
302 changes: 302 additions & 0 deletions src/rust/vendor/ryu/src/d2s.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
// Translated from C to Rust. The original C code can be found at
// https://github.com/ulfjack/ryu and carries the following license:
//
// Copyright 2018 Ulf Adams
//
// The contents of this file may be used under the terms of the Apache License,
// Version 2.0.
//
// (See accompanying file LICENSE-Apache or copy at
// http://www.apache.org/licenses/LICENSE-2.0)
//
// Alternatively, the contents of this file may be used under the terms of
// the Boost Software License, Version 1.0.
// (See accompanying file LICENSE-Boost or copy at
// https://www.boost.org/LICENSE_1_0.txt)
//
// Unless required by applicable law or agreed to in writing, this software
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.

use crate::common::{log10_pow2, log10_pow5, pow5bits};
#[cfg(not(feature = "small"))]
pub use crate::d2s_full_table::{DOUBLE_POW5_INV_SPLIT, DOUBLE_POW5_SPLIT};
use crate::d2s_intrinsics::{
div10, div100, div5, mul_shift_all_64, multiple_of_power_of_2, multiple_of_power_of_5,
};
#[cfg(feature = "small")]
pub use crate::d2s_small_table::{compute_inv_pow5, compute_pow5};
use core::mem::MaybeUninit;

pub const DOUBLE_MANTISSA_BITS: u32 = 52;
pub const DOUBLE_EXPONENT_BITS: u32 = 11;
pub const DOUBLE_BIAS: i32 = 1023;
pub const DOUBLE_POW5_INV_BITCOUNT: i32 = 125;
pub const DOUBLE_POW5_BITCOUNT: i32 = 125;

#[cfg_attr(feature = "no-panic", inline)]
pub fn decimal_length17(v: u64) -> u32 {
// This is slightly faster than a loop.
// The average output length is 16.38 digits, so we check high-to-low.
// Function precondition: v is not an 18, 19, or 20-digit number.
// (17 digits are sufficient for round-tripping.)
debug_assert!(v < 100000000000000000);

if v >= 10000000000000000 {
17
} else if v >= 1000000000000000 {
16
} else if v >= 100000000000000 {
15
} else if v >= 10000000000000 {
14
} else if v >= 1000000000000 {
13
} else if v >= 100000000000 {
12
} else if v >= 10000000000 {
11
} else if v >= 1000000000 {
10
} else if v >= 100000000 {
9
} else if v >= 10000000 {
8
} else if v >= 1000000 {
7
} else if v >= 100000 {
6
} else if v >= 10000 {
5
} else if v >= 1000 {
4
} else if v >= 100 {
3
} else if v >= 10 {
2
} else {
1
}
}

// A floating decimal representing m * 10^e.
pub struct FloatingDecimal64 {
pub mantissa: u64,
// Decimal exponent's range is -324 to 308
// inclusive, and can fit in i16 if needed.
pub exponent: i32,
}

#[cfg_attr(feature = "no-panic", inline)]
pub fn d2d(ieee_mantissa: u64, ieee_exponent: u32) -> FloatingDecimal64 {
let (e2, m2) = if ieee_exponent == 0 {
(
// We subtract 2 so that the bounds computation has 2 additional bits.
1 - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS as i32 - 2,
ieee_mantissa,
)
} else {
(
ieee_exponent as i32 - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS as i32 - 2,
(1u64 << DOUBLE_MANTISSA_BITS) | ieee_mantissa,
)
};
let even = (m2 & 1) == 0;
let accept_bounds = even;

// Step 2: Determine the interval of valid decimal representations.
let mv = 4 * m2;
// Implicit bool -> int conversion. True is 1, false is 0.
let mm_shift = (ieee_mantissa != 0 || ieee_exponent <= 1) as u32;
// We would compute mp and mm like this:
// uint64_t mp = 4 * m2 + 2;
// uint64_t mm = mv - 1 - mm_shift;

// Step 3: Convert to a decimal power base using 128-bit arithmetic.
let mut vr: u64;
let mut vp: u64;
let mut vm: u64;
let mut vp_uninit: MaybeUninit<u64> = MaybeUninit::uninit();
let mut vm_uninit: MaybeUninit<u64> = MaybeUninit::uninit();
let e10: i32;
let mut vm_is_trailing_zeros = false;
let mut vr_is_trailing_zeros = false;
if e2 >= 0 {
// I tried special-casing q == 0, but there was no effect on performance.
// This expression is slightly faster than max(0, log10_pow2(e2) - 1).
let q = log10_pow2(e2) - (e2 > 3) as u32;
e10 = q as i32;
let k = DOUBLE_POW5_INV_BITCOUNT + pow5bits(q as i32) - 1;
let i = -e2 + q as i32 + k;
vr = unsafe {
mul_shift_all_64(
m2,
#[cfg(feature = "small")]
&compute_inv_pow5(q),
#[cfg(not(feature = "small"))]
{
debug_assert!(q < DOUBLE_POW5_INV_SPLIT.len() as u32);
DOUBLE_POW5_INV_SPLIT.get_unchecked(q as usize)
},
i as u32,
vp_uninit.as_mut_ptr(),
vm_uninit.as_mut_ptr(),
mm_shift,
)
};
vp = unsafe { vp_uninit.assume_init() };
vm = unsafe { vm_uninit.assume_init() };
if q <= 21 {
// This should use q <= 22, but I think 21 is also safe. Smaller values
// may still be safe, but it's more difficult to reason about them.
// Only one of mp, mv, and mm can be a multiple of 5, if any.
let mv_mod5 = (mv as u32).wrapping_sub(5u32.wrapping_mul(div5(mv) as u32));
if mv_mod5 == 0 {
vr_is_trailing_zeros = multiple_of_power_of_5(mv, q);
} else if accept_bounds {
// Same as min(e2 + (~mm & 1), pow5_factor(mm)) >= q
// <=> e2 + (~mm & 1) >= q && pow5_factor(mm) >= q
// <=> true && pow5_factor(mm) >= q, since e2 >= q.
vm_is_trailing_zeros = multiple_of_power_of_5(mv - 1 - mm_shift as u64, q);
} else {
// Same as min(e2 + 1, pow5_factor(mp)) >= q.
vp -= multiple_of_power_of_5(mv + 2, q) as u64;
}
}
} else {
// This expression is slightly faster than max(0, log10_pow5(-e2) - 1).
let q = log10_pow5(-e2) - (-e2 > 1) as u32;
e10 = q as i32 + e2;
let i = -e2 - q as i32;
let k = pow5bits(i) - DOUBLE_POW5_BITCOUNT;
let j = q as i32 - k;
vr = unsafe {
mul_shift_all_64(
m2,
#[cfg(feature = "small")]
&compute_pow5(i as u32),
#[cfg(not(feature = "small"))]
{
debug_assert!(i < DOUBLE_POW5_SPLIT.len() as i32);
DOUBLE_POW5_SPLIT.get_unchecked(i as usize)
},
j as u32,
vp_uninit.as_mut_ptr(),
vm_uninit.as_mut_ptr(),
mm_shift,
)
};
vp = unsafe { vp_uninit.assume_init() };
vm = unsafe { vm_uninit.assume_init() };
if q <= 1 {
// {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0 bits.
// mv = 4 * m2, so it always has at least two trailing 0 bits.
vr_is_trailing_zeros = true;
if accept_bounds {
// mm = mv - 1 - mm_shift, so it has 1 trailing 0 bit iff mm_shift == 1.
vm_is_trailing_zeros = mm_shift == 1;
} else {
// mp = mv + 2, so it always has at least one trailing 0 bit.
vp -= 1;
}
} else if q < 63 {
// TODO(ulfjack): Use a tighter bound here.
// We want to know if the full product has at least q trailing zeros.
// We need to compute min(p2(mv), p5(mv) - e2) >= q
// <=> p2(mv) >= q && p5(mv) - e2 >= q
// <=> p2(mv) >= q (because -e2 >= q)
vr_is_trailing_zeros = multiple_of_power_of_2(mv, q);
}
}

// Step 4: Find the shortest decimal representation in the interval of valid representations.
let mut removed = 0i32;
let mut last_removed_digit = 0u8;
// On average, we remove ~2 digits.
let output = if vm_is_trailing_zeros || vr_is_trailing_zeros {
// General case, which happens rarely (~0.7%).
loop {
let vp_div10 = div10(vp);
let vm_div10 = div10(vm);
if vp_div10 <= vm_div10 {
break;
}
let vm_mod10 = (vm as u32).wrapping_sub(10u32.wrapping_mul(vm_div10 as u32));
let vr_div10 = div10(vr);
let vr_mod10 = (vr as u32).wrapping_sub(10u32.wrapping_mul(vr_div10 as u32));
vm_is_trailing_zeros &= vm_mod10 == 0;
vr_is_trailing_zeros &= last_removed_digit == 0;
last_removed_digit = vr_mod10 as u8;
vr = vr_div10;
vp = vp_div10;
vm = vm_div10;
removed += 1;
}
if vm_is_trailing_zeros {
loop {
let vm_div10 = div10(vm);
let vm_mod10 = (vm as u32).wrapping_sub(10u32.wrapping_mul(vm_div10 as u32));
if vm_mod10 != 0 {
break;
}
let vp_div10 = div10(vp);
let vr_div10 = div10(vr);
let vr_mod10 = (vr as u32).wrapping_sub(10u32.wrapping_mul(vr_div10 as u32));
vr_is_trailing_zeros &= last_removed_digit == 0;
last_removed_digit = vr_mod10 as u8;
vr = vr_div10;
vp = vp_div10;
vm = vm_div10;
removed += 1;
}
}
if vr_is_trailing_zeros && last_removed_digit == 5 && vr % 2 == 0 {
// Round even if the exact number is .....50..0.
last_removed_digit = 4;
}
// We need to take vr + 1 if vr is outside bounds or we need to round up.
vr + ((vr == vm && (!accept_bounds || !vm_is_trailing_zeros)) || last_removed_digit >= 5)
as u64
} else {
// Specialized for the common case (~99.3%). Percentages below are relative to this.
let mut round_up = false;
let vp_div100 = div100(vp);
let vm_div100 = div100(vm);
// Optimization: remove two digits at a time (~86.2%).
if vp_div100 > vm_div100 {
let vr_div100 = div100(vr);
let vr_mod100 = (vr as u32).wrapping_sub(100u32.wrapping_mul(vr_div100 as u32));
round_up = vr_mod100 >= 50;
vr = vr_div100;
vp = vp_div100;
vm = vm_div100;
removed += 2;
}
// Loop iterations below (approximately), without optimization above:
// 0: 0.03%, 1: 13.8%, 2: 70.6%, 3: 14.0%, 4: 1.40%, 5: 0.14%, 6+: 0.02%
// Loop iterations below (approximately), with optimization above:
// 0: 70.6%, 1: 27.8%, 2: 1.40%, 3: 0.14%, 4+: 0.02%
loop {
let vp_div10 = div10(vp);
let vm_div10 = div10(vm);
if vp_div10 <= vm_div10 {
break;
}
let vr_div10 = div10(vr);
let vr_mod10 = (vr as u32).wrapping_sub(10u32.wrapping_mul(vr_div10 as u32));
round_up = vr_mod10 >= 5;
vr = vr_div10;
vp = vp_div10;
vm = vm_div10;
removed += 1;
}
// We need to take vr + 1 if vr is outside bounds or we need to round up.
vr + (vr == vm || round_up) as u64
};
let exp = e10 + removed;

FloatingDecimal64 {
exponent: exp,
mantissa: output,
}
}
Loading

0 comments on commit 83cd6c8

Please sign in to comment.